找回密码
 立即注册
首页 业界区 安全 图解C++智能指针的循环引用

图解C++智能指针的循环引用

皇甫佳文 前天 10:05
欢迎大家访问我的个人主页guts的小屋
循环引用是学习智能指针过程中的一个小难点,笔者愚钝,明明知道是两个指针互相引用导致了内存泄漏,但看各种文字资料时,脑子里总是一团浆糊,感觉似懂非懂,于是自己绘制了几张图片,思路和概念才清晰起来,希望这篇博客能帮助有同样困惑的同学解惑。
shared_ptr 的实现

要明白循环引用,我们首先需要知道shared_ptr是如何实现的,它内部包含两个指针,一个指向我们要管理的资源对象,另一个指向则指向这个对象所对应的控制块。在这个控制块中,包含了这个对象的强引用计数,当我们第一次声明一个shared_ptr变量时,就会为这个资源对象分配一个控制块,并将强引用计数初始化为1。
当有新的shared_ptr对象拥有这个资源的时候,强引用计数就会+1,当shared_ptr对象销毁或者调用.reset()方法时,强引用计数就会-1,当强引用计数为0时,这个资源就会析构。这样我们就通过RAII来避免了内存的泄露。
循环引用

这看起来是一个很完美的设计,但是shared_ptr管理的资源和shared_ptr对象本身是两块内存,并且shared_ptr管理的资源本身也可以包含一个shared_ptr对象并持有资源,这样就会导致,一旦两个资源本身互相持有,那么他们析构的条件就是对方析构,这就会导致内存的泄露。
这么说可能有些模糊,我们直接来看例子:
  1. class A {public:    std::shared_ptr ptr;    ...}int main() {    // code I    std::shared_ptr a = std::make_shared();    std::shared_ptr b = std::make_shared();     // code II    a->ptr = b;    b->ptr = a;    return 0;}
复制代码
code I 运行完毕后,我们会在堆中创建两个A类的对象,我们将其称之为OBJ_A和OBJ_B,同时栈上会有两个std::shared_ptr对象a和b,他们分别指向OBJ_A和OBJ_B以及他们各自的控制块CB_A和CB_B,如下图所示

此时两个控制块的引用计数都是1,当我们运行Code II后,OBJ_A和OBJ_B会拥有对对方的所有权,他们的引用计数都变为2

这个时候代码运行完毕,a和b作为栈上的对象被销毁,OBJ_A和OBJ_B的引用计数都下降为1,但是由于此时他们都持有指向彼此的shared_ptr对象,引用计数仍为1,不会继续下降,这块内存就不会被释放,造成了内存泄露。

weak_ptr的引入

为了解决这个问题,我们引入了weak_ptr,它不拥有资源的所有权,指向资源的时候只会增加弱引用计数而非强引用计数,弱引用计数不决定资源的生命周期,只决定控制块的生命周期,当弱引用计数为0时销毁控制块。
需要注意的是,shared_ptr也会让弱引用计数+1。
  1. class A {public:    std::weak_ptr ptr;    ...}int main() {    std::shared_ptr a = std::make_shared();    std::shared_ptr b = std::make_shared();    a->ptr = b;    b->ptr = a;    return 0;}
复制代码
当我们使用上面的代码时,几个对象的声明周期如下图所示:

在b和a离开作用域后

  • b析构,CB_B的强引用计数-1为0,弱引用计数-1=1
  • OBJ_B析构,CB_A的弱引用计数-1=1
  • a析构,CB_A的强引用计数-1为0,弱引用计数-1=0
  • OBJ_A和CB_A析构,CB_B的弱引用计数-1=0
  • CB_B析构

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册