如何防止智能指针循环引用问题

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何防止智能指针循环引用问题相关的知识,希望对你有一定的参考价值。

参考技术A 同样的东西,名称有所不同而已Java:java.lang.ref.WeakReferencePython:weakref通常的垃圾回收有两种方式,一种是引用计数(referencecounting)也就是利用智能指针,另一种是循环垃圾回收器(cyclicgarbagecollector)。后者可以直接处理循环引用,不过通常不能及时调用析构方法。Java和python都有循环垃圾回收器,所以弱引用只是一个补充而已

智能指针循环引用——你真的懂了吗?

相信不少同学都在面试中都被问到过c++智能指针的问题,接踵而至的必定是循环引用了,而我每次的答案都是一招鲜:因为它们都在互相等待对方先释放,所以造成内存泄漏。面试官很满意,我也很满意。

但是为啥要等到对方先释放?在我内心也曾有个问号。。

智能指针起源

所谓人类进步的阶梯就是懒,曾几何时,就有人想,我把new 运算符返回的指针p交给一个对象“托管”,而不用操心在哪里去释放这个指针p,由这个托管者自动的在合适的时机进行指针p的释放,这样也不怕自己忘记释放指针p了。而且,我这边操作托管者,要跟操作原来的指针一毛一样,这样才方便。即假设托管指针p的对象叫做shared_ptr,那么

*shared_ptr 所指,便是 *p 所向!

第一章 auto_ptr

最早的智能指针类,实现了*运算符。你用它实例化出来一个类对象,用起来就好像指针一样。对象的特性自然是在作用域结束时,自动调用析构函数实现自我销毁。如此一来,攻城狮们便可不再操心何时释放指针,而可以随心所欲的摸鱼啦。

但其有个致命缺陷:因为其缺省的复制和赋值构造函数,带来的同一个对象被多个智能指针托管,释放的时候便混乱异常,一不小心就重复释放了,使得用户使用起来战战兢兢,老早就被淘汰了。

第二章 unique_ptr

为了规避auto_ptr可以随意复制和赋值构造的缺陷,就推出了unique_ptr, 其实就是给auto_ptr简单换了个名字,并禁用掉其默认的复制和赋值构造函数,好嘛,大家都别用了<(`^′)>,不写代码,就不会有bug!

第三章 shared_ptr

只有一个unique_ptr,对于省吃俭用的高级攻城狮们来说自然是不够的,于是就有人站了出来喊了一声:我们要搞共享经济——砰!基于引用计数的shared_ptr横空出世了,每多一个人持有这个对象,引用计数就加一;每少一个人持有这个对象,引用计数就减一。引用计数为零时,才做真正的销毁操作。

很快,梦魇悄然而至,“我的shared_ptr怎么没有释放?”——一位焦头烂额的格子少年路过。。

起初,没有人在意这场灾难,这不过是一个指针的丢失、一个bug,一个服务器的宕机,直到这场灾难和每个人息息相关......

"你看这个人写的代码,它好像一坨狗屎"

#include <iostream>

class B;
class A 
public:
    A() 
        std::cout << "A" << std::endl;
    
    ~A() 
        std::cout << "~A" << std::endl;
    
public:
	std::shared_ptr<B> ptr;
;

class B 
public:
    B() 
        std::cout << "B" << std::endl;
    
    ~B() 
        std::cout << "~B" << std::endl;
    
public:
	std::shared_ptr<A> ptr;
;

void fun() 
	std::shared_ptr<A> pa(new A());
	std::shared_ptr<B> pb(new B());
	pa->ptr = pb;
	pb->ptr = pa;


int main()

	fun();
	return 0;

运行结果我不敢看: (*/ω\*)

纳尼?A和B的析构函数都没有被调用,妥妥的内存泄漏了! 

事后,据某位亲身经历这次事件的大牛回忆说:“喔,当时的内存布局是这个样子的!”

“对象A同时被pa和对象B中的ptr两个智能指针托管,所以引用计数为2;对象B同时被pb和对象
A中的ptr两个智能指针托管,所以引用计数也为2。那么当fun函数执行完,栈对象pb、pa依次开始执行这样的析构函数:”

// 大牛随手写的析构函数伪代码
// Copyright 2022 DaNiu. All rights reserved.

if (--ref_cnt == 0) delete obj;

“紧接着内存布局就变成了这样:”

“pa和pb已经销毁了,然而对象A和B,已经迷失在这浩瀚内存中,亘古难灭。。。吾称之为循环引用!”

路人震惊:“嘶!随着这样的迷失越来越多,这片天地再也无人可搞对象!!!”

゜゜(´O`) ゜゜。:“不要啊!我还没有搞过对象呢。”

......

庆幸的是,不就之后,有人就在shared_ptr出世的那方世界的一个不起眼的角落里,发现了一个小家伙,伴shared_ptr而生。

终章 weak_ptr

为了解决shared_ptr在在循环引用中存在的资源泄漏问题,weak_ptr在这种场景下应用而生,weak_ptr指向的智能指针对象,其引用计数不会加一,也就不会存在无法释放的问题了。

解决的方法就是,把A和B其中的一个ptr改成weak_ptr。

以上是关于如何防止智能指针循环引用问题的主要内容,如果未能解决你的问题,请参考以下文章

C++智能指针及循环引用

智能指针循环引用--转

智能指针的循环引用与解决

智能指针循环引用——你真的懂了吗?

智能指针循环引用——你真的懂了吗?

shared_ptr智能指针为什么循环引用会出问题