浅谈shared_ptr与循环引用问题

前面对于智能指针已经大致的提过了(http://blog.csdn.net/chenkaixin_1024/article/details/69276679),但是关于shared_ptr上一篇文章没有仔细分析,这里把它单独拎出来理一理。

前面已经提过由于scoped_ptr(unique_ptr)无法解决让多个对象管理同一块空间的情况,标准库中又追加引用了shared_ptr,而我们的shared_ptr是参考了string类当中的引用计数这个巧妙的方法来解决多个对象管理同一块空间的问题的,而且除此之外我们这里的模拟实现还要考虑跟多一点,就是加入删除器的部分,以适用不同的指针。到这里如果有不清楚可以参考上一篇博客,链接就在文章开头。

我在这篇博客想要重点讨论一下shared_ptr所存在的循环引用的问题:

#include   
using namespace std;  
  
#include   
#include "SharedPtr.h"  
  
struct Node  
{  
    Node(int va)  
        :value(va)  
          
    {  
        cout<<"Node()"< _pre;  
    shared_ptr _next;  
    int value;  
};  
  
void funtest()  
{  
    shared_ptr sp1(new Node(1));  
    shared_ptr sp2(new Node(2));  
  
    sp1->_next=sp2;  
    sp2->_pre=sp1;  
}  
int main()  
{  
    funtest();  
    return 0;  
}  

上述程序在执行完sp2->_pre=sp1;这条语句之后,也就是在构建完两个节点相互指向之后,大致模型如图:

浅谈shared_ptr与循环引用问题_第1张图片

shared_ptr的对象模型大致分为指向引用计数空间的指针_Ref和指向节点空间的_ptr,而我们引用计数空间在标准库中其实有两个long类型的计数值use和weak,而且在空间被创建成功后,初始化都给成1。

由于一块空间被另外一个shared_ptr的对象所管理,只会增加引用计数中use的值,至于weak的值,后面再说。

根据上面的测试用例,我们知道sp1,sp2,_pre,_next均是shared_ptr的对象,所以上图中两个引用计数空间中的use均为2,weak均为1,而在出funtest()的作用域之前,会对栈空间上的变量进行销毁释放,也就是说在这里,会对sp1和sp2这两个对象进行释放,调用它们的析构函数,但由于在shared_ptr的析构函数中,只有当use=1,进行减减之后为0,才会释放_ptr所指向的空间,所以在这里sp1和sp2所管理的节点空间是不会被释放的,因此也不会调用~Node()这个析构函数,所以这里就出现了上篇文章末尾所出现的问题,也就是内存泄漏。

由于在shared_ptr单独使用的时候会出现循环引用的问题,造成内存泄漏,所以标准库又从boost库当中引入了weak_ptr。

对上面的测试用例进行修改:

#include   
using namespace std;  
  
#include   
#include "SharedPtr.h"  
  
struct Node  
{  
    Node(int va)  
        :value(va)  
          
    {  
        cout<<"Node()"< _pre;  
    weak_ptr _next;  
    int value;  
};  
  
void funtest()  
{  
    shared_ptr sp1(new Node(1));  
    shared_ptr sp2(new Node(2));  
  
    sp1->_next=sp2;  
    sp2->_pre=sp1;  
}  
int main()  
{  
    funtest();  
    return 0;  
}  
其实也并没有做太大的修改,只是将其中的_pre和_next的类型换成weak_ptr,当这个测试用例运行到sp2->_pre=sp1结束后,节点之间的相互关系与上图基本无差别,但是这里有一点发生了变化,就是由于每个节点仅有一个shared_ptr的对象管理,这里两块引用计数空间中的use均为1,而由于多了weak_ptr的对象_pre或_next指向,这里weak的值就变成了2,因此根据前面的讨论,两个节点空间就成功释放了:

浅谈shared_ptr与循环引用问题_第2张图片
而这里关于这个空间到底是如何释放的以及对于引用计数的空间的释放还要进一步讨论:

下面跟着测试用例逐步调试空间的释放过程(大致如下):

浅谈shared_ptr与循环引用问题_第3张图片


首先,先对sp2这一share_ptr对象进行释放,调用其析构函数(即第1步),紧跟着向后走,走到第3步,先对当前use进行减减,并判断是否为0,这里由于use之前为1,这里刚好就等于0;所以进入if语句内,调用_Destroy(),释放_ptr所管理的资源(这里即为节点),所以调用~Node(),对sp2所管理的节点进行析构,这里注意的是节点的结构中有weak_ptr类型的对象_pre和_next;而这里sp2->_pre=sp1,所以要对_pre进行析构必然要调用其析构函数,调用过程大致如下:浅谈shared_ptr与循环引用问题_第4张图片

在第8步,对sp1的weak进行减减(注意:这里是对sp1对应的引用计数中的weak减减,使其为1)尝试释放sp1对应的引用计数空间,但由于weak在这里为1,所以对sp1的引用计数空间并未能释放(未能进行第9步调用);
在对sp2所管理的节点释放之后,就完成了之前第4步的调用,接下来进行第5步调用_Decwref(),尝试释放sp2所对应的引用计数空间,但由于这里weak等于2,- -weak之后,weak=1,所以并不能释放对应引用计数空间,到这里为止,就完成对sp2对象的释放;

随后进行sp1的释放,大致与sp2的释放相同,但是在执行第8步的时候,由于之前已经对sp2的weak减减过一次了,这里再减减就等于0,进行对sp2的引用计数空间的释放;随后在第5步的操作时,由于之前已经对sp1的weak减减过一次了,这里再减减就等于0,进行对sp1的引用计数空间的释放;而对于sp1所管理的节点,与sp2的情况相同在第4步进行了释放。到此为止,所有的空间都得到了释放(包括引用计数空间),因此这里就不会发生内存泄漏。

从头到尾过程大致如下:

浅谈shared_ptr与循环引用问题_第5张图片




你可能感兴趣的:(c++)