天天看点

为什么c++智能指针shared_ptr要慎用取引用?

作者:IT民工冯老师

前言

这几天在项目中发现base代码中有个关于指针shared_ptr引用使用问题,现在把问题的原因和写的demo给大家分享一下,供大家参考。

问题内容

代码中有个全局数据存储上下文信息,供UI某查询界面对查询条件进翻页分段查询。

为什么c++智能指针shared_ptr要慎用取引用?

界面示意图

后台系统收到UI的新查询时,此时请求中标志是“新请求/旧请求”标记为新请求,后台则创建并存储一个对应的“上下文信息”,用于标记当前查询条件的操作,随后到数据库查询对应条件的全量数据并存在后台系统内存中,后台系统向UI返回一定个数的数据;如果是当前查询的翻页操作,UI也会发起查询请求,此时请求中的标志为“旧请求”,后台系统需要找到对应的上下文信息并在内存中进行数据读取。

后台系统在“上下文信息”中存储了某个智能指针数据ptr,在后台的具体查询函数中,新定义了这个类型的智能指针引用类型auto & temp存储ptr数据,但是最后在使用完后,显示执行了智能指针的释放(temp=nullptr;),导致原智能指针ptr数据被释放.....

为什么c++智能指针shared_ptr要慎用取引用?

问题产生原因

把“智能指针shared_ptr”赋值给新定义新指针变量引用类型后,引用计数并没有增加, 引用变量设置为nullptr后,原智能指针内部数据也已经无效。

智能指针shared_ptr介绍

shared_ptr允许多个该智能指针共享“拥有”同一堆分配对象的内存,这通过引用计数(reference counting)实现,会记录有多少个shared_ptr共同指向一个对象,一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除。支持复制和赋值操作。

  • shared_ptr 类对象默认初始化为一个空指针。智能指针是模板,创建智能指针必须提供类型信息。

如:

shared_ptr<string> p1; // shared_ptr,可以指向 string

shared_ptr<list<int>> p2; // shared_ptr,可以指向 int 的 list

  • 智能指针的使用和普通指针类似,解引用一个智能指针返回它指向的对象,如过在条件判断中使用它,则是检测其是否为空。
  • 当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象。
  • 每个shared_ptr都有一个关联的计数器,通常称其为引用计数:

对shared_ptr类进行拷贝时,计数器就会增加(用一个shared_ptr初始化另一个shared_ptr、或者它作为参数传递给一个函数以及作为函数的返回值)它所关联的计数器就会增加;

当我们让shared_ptr指向另一个对象或shared_ptr对象销毁时,原对象的计数器就会递减,一旦一个shared_ptr的计数器为0,就会自动释放该对象的内存。

为什么c++智能指针shared_ptr要慎用取引用?

c++引用

引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。引用的声明方法:类型标识符 &引用名=目标变量名;

  • 引用引入了对象的一个同义词,定义引用的表示方法与定义指针相似,只是用&代替了*。
  • 对引用求地址,就是对目标变量求地址。
为什么c++智能指针shared_ptr要慎用取引用?

智能指针shared_ptr取引用的计数效果

  • 把“智能指针shared_ptr”赋值给新定义新指针变量引用类型后,引用计数并没有增加, 引用变量设置为nullptr后,原智能指针内部数据也已经无效
  • 把“智能指针shared_ptr”赋值给新定义新指针变量后,引用计数增加,新指针设置为nullptr后,原智能指针内部数据不影响

(可以通过对下列代码15行处 #if 后数值改成0或1进行验证)

#include <iostream>  
#include <memory>  
  
int main() {  
  std::shared_ptr<int> ptr(new int(5));  
  std::cout << "count: " << ptr.use_count() << std::endl; // 打印引用计数, 1

  // 获取原始指针并进行赋值操作  
  int *p = ptr.get();  
  *p = 10;  
  
  // 输出结果,发现值已经被修改  
  std::cout << *ptr << std::endl; // 输出 10  

#if 1
//把“智能指针shared_ptr”赋值给新定义新指针变量引用类型后,引用计数并没有增加, 引用变量设置为nullptr后,原智能指针内部数据也已经无效
auto &t = ptr;
std::cout << "count: " << ptr.use_count() << std::endl; // 打印引用计数, 1
std::cout << t.get() << std::endl;//0x1331b20
std::cout << ptr.get() << std::endl;//0x1331b20

t = nullptr;// 显式销毁所指对象
std::cout << "count: " << ptr.use_count() << std::endl; // 打印引用计数, 1
std::cout << p  << std::endl;//0x1331b20
std::cout << ptr  << std::endl;//0
//std::cout << *ptr  << std::endl;//异常
std::cout << ptr.get() << std::endl;//0
#else
//把“智能指针shared_ptr”赋值给新定义新指针变量后,引用计数增加,新指针设置为nullptr后,原智能指针内部数据不影响
auto t = ptr;  
std::cout << "count: " << ptr.use_count() << std::endl; // 打印引用计数, 2
std::cout << t.get() << std::endl;//0x2220b20
std::cout << ptr.get() << std::endl;//0x2220b20

t = nullptr;// 显式销毁所指对象
std::cout << "count: " << ptr.use_count() << std::endl; // 打印引用计数, 1
std::cout << p  << std::endl;//0x2220b20
std::cout << ptr  << std::endl;//0x2220b20
std::cout << *ptr  << std::endl;//输出 10  
#endif

  return 0;  
}           

我们再看看通过函数入参传入智能指针的效果:

(可以通过对下列代码11行处 #if 后数值改成0或1进行验证)

#include <iostream>  
#include <memory>  

struct s_test_
{
    std::shared_ptr<int> ptr;
};

void fun(s_test_ & s_temp)
{
#if 1
    //智能指针shared_ptr取引用后,引用计数并没有增加, 引用变量设置为nulls_temp.ptr后,原智能指针内部数据也已经无效
    auto &t = s_temp.ptr;
    int *p = t.get();  
    *p = 20;  
    std::cout << "s_temp.ptr count: " << s_temp.ptr.use_count() << std::endl; // 打印引用计数, 1
    std::cout << t.get() << std::endl;//0x1331b20
    std::cout << s_temp.ptr.get() << std::endl;//0x1331b20

    t = nullptr;// 显式销毁所指对象
    std::cout << "s_temp.ptr count: " << s_temp.ptr.use_count() << std::endl; // 打印引用计数, 1
    std::cout << p  << std::endl;//0x1331b20
    std::cout << s_temp.ptr  << std::endl;//0
    //std::cout << *(s_temp.ptr) << std::endl;//异常
    std::cout << s_temp.ptr.get() << std::endl;//0
#else
    //定义新指针指向“智能指针shared_ptr”后,引用计数增加,新指针设置为nulls_temp.ptr后,原智能指针内部数据不影响
    auto t = s_temp.ptr;  
    int *p = t.get();  
    *p = 20;  
    std::cout << "s_temp.ptr count: " << s_temp.ptr.use_count() << std::endl; // 打印引用计数, 2
    std::cout << t.get() << std::endl;//0x2220b20
    std::cout << s_temp.ptr.get() << std::endl;//0x2220b20

    t = nullptr;// 显式销毁所指对象
    std::cout << "s_temp.ptr count: " << s_temp.ptr.use_count() << std::endl; // 打印引用计数, 1
    std::cout << p  << std::endl;//0x2220b20
    std::cout << s_temp.ptr  << std::endl;//0x2220b20
    std::cout << *(s_temp.ptr)  << std::endl;//输出 20  
#endif
}

int main() 
{  
    s_test_ s1;
    s1.ptr = std::make_shared<int>(5);
    std::cout << "s1.ptr count: " << s1.ptr.use_count() << std::endl; // 打印引用计数, 1

    // 获取原始指针并进行赋值操作  
    int *p = s1.ptr.get();  
    *p = 10;  

    // 输出结果,发现值已经被修改  
    std::cout << *(s1.ptr) << std::endl; // 输出 10  

    fun(s1);
    std::cout << *(s1.ptr) << std::endl; // 输出 20  

    return 0;  
}           

demo验证结果:

为什么c++智能指针shared_ptr要慎用取引用?

智能指针shared_ptr取引用

为什么c++智能指针shared_ptr要慎用取引用?

定义新指针指向“智能指针shared_ptr”

继续阅读