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

来源:互联网 发布:手机收发邮件软件 编辑:程序博客网 时间:2024/05/22 09:21
       当我们学会使用智能指针的时候,会发现他有很多好处,但库里面提供的智能指针shared_ptr也并不是万能的,他会存在循环引用的问题,下面我们就通过实例来具体分析循环引用这种情况。
struct  Node{Node( const T&data):_data(data), _Pre(NULL) ,_Pnext(NULL){}shared_ptr<Node<T>> _Pre;shared_ptr<Node<T>> _Pnext;T _data;~Node(){cout << "~Node()" << endl;}};void FunTest(){    shared_ptr<Node<int>> sp1(new Node<int>(10));    shared_ptr<Node<int>> sp2(new Node<int>(20));cout << sp1.use_count() << endl;cout << sp2.use_count() << endl;sp1->_Pnext = sp2;sp2->_Pre = sp1;cout << sp1.use_count() << endl;cout << sp2.use_count() << endl;}int main(){FunTest();getchar();return 0;}
       在这个例子中,我们自定义了一个节点类型Node,它包含两个智能指针,一个_Pnext指向下一个节点,另一个_Pre指向前一个节点,还包含值域data,并且给出了它的构造函数和析构函数,在析构函数中打印析构函数名,以测试是否调用,在FunTest()函数中又通过智能指针创建了两个节点对象sp1和sp2.然后分别打印sp1和sp2的引用计数,在这里下一个断点,观察程序运行结果。

接下来,把两个节点连接起来。再打印它们的引用计数。
出了FunTest函数的作用域,两个对象应该被销毁,来看看程序 运行结果。

并没有打印出析构函数名,这就说明析构函数没有被调用,这就存在内存泄漏,而这种情况就是因为循环引用造成的,为了解释循环引用的原理,我们先分析shared_ptr的构造原理。
我们通过跟进库函数一步一步得知它的结构如下。
      利用上图我们来理解shaerd_ptr的结构,它首先公有继承自一个基类_Ptr_base,这个基类包含了两个成员,一个是T*_Ptr,另外一个是_Ref_count_base类型的指针_Rep,通过查看定义我们查看了_Ref_count_base这个基类的结构,他是一个抽象类,它有两个成员_Uses和_Weaks,不难发现,他们就是引用计数,并且在构造这个基类时这俩引用计数都置为1。
        并且在库里面有三个类继承自这个基类。_Ref_count只有一个_Ptr,他有两个销毁函数_Dsetroy()和_Delete_this(),_Ref_count_del是带定制删除器的引用计数类,它销毁空间时需要调用定制删器即可。而_Ref_count_del_alloc是带有删除器和空间配置器的引用计数类。_Ptr最后指向节点空间,_Ref_count_base*Ref这个基类指针最后指向引用计数的对象。
下面是sp1的构造过程:
1.申请出节点空间。
2.调用shared_ptr的构造函数。
3.构造基类_Ptr_base,使_Ptr指向节点空间,new出引用计数_Ref_count。
4.构造引用计数,先构造引用计数的基类使_Uses和_Weaks为1。
5.构造引用计数_Ref_count的_ptr使其指向节点空间。
当我们将两个节点连接起来之后,sp1和sp2的引用计数_Uses都将增加为2,出了函数的作用域,先销毁sp2,将它的引用计数减为1,不为0;变量销毁,而节点空间并没有释放。同样的销毁sp1时,引用计数减为1不为0,销毁变量,空间没有释放。这两个对象相互咬着谁都不肯放。这就是循环引用。怎么解决这个问题呢,我们采用的是weak_ptr,注意:weak_ptr不能单独使用管理空间,我们将代码重写如下。
#include<iostream>#include<memory>using namespace std;template<class T>struct  Node{Node(const T&data):_data(data){}weak_ptr<Node<T>> _Pre;weak_ptr<Node<T>> _Pnext;T _data;~Node(){cout << "~Node():" <<this<< endl;}};void FunTest(){    shared_ptr<Node<int>> sp1(new Node<int>(10));    shared_ptr<Node<int>> sp2(new Node<int>(20));cout << sp1.use_count() << endl;cout << sp2.use_count() << endl;sp1->_Pnext = sp2;sp2->_Pre = sp1;cout << sp1.use_count() << endl;cout << sp2.use_count() << endl;}int main(){FunTest();getchar();return 0;}
这次我们观察运行结果。

两个节点成功的被销毁,下面我们来看看weak_ptr的构成以及原理。

1.首先weak_ptr和shared_ptr一样都是共有继承自_Ptr_base。
2.它和shared_ptr的构造大体上相同,不过在增加引用计数的值时,shared_ptr是增加_Uses的值,而weak_ptr是增加_Weaks的值。同样在类对象销毁时,share_ptr通过_Uses减1是否为0来判断是否销毁,而weak_ptr是通过_Weak减1是否为0来判断。

这是两个节点的连接过程的示意图:

下面我们来一步步看连接过程:(ref为引用计数)
1.sp1的_Pnext中的ptr指向第二个节点,sp1的引用计数ref和sp2的引用计数共用,此时保持sp2引用计数中的Uses1不变,使Weaks增加为2;
2.同样的,使sp2中的_Pre的ptr指向第一个节点,然后让它的ref共用第sp1的引用计数,使sp1引用计数中的Uses不变,Weaks变为2;
出了函数的作用域,来销毁节点:
1.让sp2引用计数中的Uses从1减为0,调用sp2的析构函数,将sp2节点销毁。
2.sp2被销毁,所以它其中的_Pre也被销毁,因为sp2中_Pre被sp1所引用,所以为sp1的引用计数中的Weaks从2减为1;
3.sp2引用计数中Weaks从2减为1不为0,所以sp2的引用计数空间不会被销毁;
4.销毁sp1,sp1的Uses从1减为0,销毁sp1这个节点,调用sp1的析构函数;
5.sp1被销毁,sp1中的_PNext也被销毁,因为sp1的_PNext被sp2所引用,所以sp1中的Weak减1为0,销毁sp2的引用计数空间;
6,sp1的Weks从1减为0,销毁sp1的引用计数空间;
就这样,四块空间被销毁,避免了循环引用带来的内存泄漏问题。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 两证合一后国税怎么办 杭州的发票丢了怎么办 小车登记证丢了怎么办 个体户地税逾期未申报怎么办 公司社保本丢了怎么办 社保红本子掉了怎么办 三证合一后逾期怎么办 个体执照没办国税地税怎么办 丰巢APP注册没工牌号怎么办 农业银行k宝证书过期怎么办 个体户网上申报税没定期怎么办 遇到不给开票的商户怎么办 奶茶店电脑下单怎么办 个体户营业执照注销怎么办清税业务 注册公司没有办公地点怎么办 变更莒业执照法人怎么办手续 欠了几十万贷款怎么办 杭州公司跨区迁址怎么办 公司同市内跨区迁址怎么办 合同写错了字怎么办 农行卡转工行卡怎么办 外地人在北京交社保退休怎么办 身份证掉了单位宿舍怎么办居住证 广州租住单位宿舍怎么办居住证 公司u盾丢了怎么办 北京办了居住卡怎么办延期 商铺被陌生人注册左公司怎么办 营业执照年检登录密码忘了怎么办 欠人家钱没钱还怎么办 欠钱实在没钱还怎么办 党关系丢了10年怎么办? 离婚后生孩子怎么办出生证明 注册公司没有注册地址怎么办 银行流水不够2倍怎么办 个体户小店怎么办五险 姓和名五行相克怎么办? 三星s7刷机后计算器没有了怎么办 线雕鼻子山根鼓怎么办 在日本没有日币怎么办 明知合同回扣特别高怎么办 医院药品断货了怎么办