智能指针与弱指针解决循环引用
来源:互联网 发布:手机收发邮件软件 编辑:程序博客网 时间: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的引用计数空间;
就这样,四块空间被销毁,避免了循环引用带来的内存泄漏问题。
阅读全文
1 0
- 智能指针与弱指针解决循环引用
- C++智能指针循环引用解决
- C++编程智能指针循环引用解决
- C++编程智能指针循环引用解决
- C++智能指针循环引用解决
- 智能指针 -- 循环引用
- 智能指针的循环引用和如何解决循环引用
- [C++] 智能指针与循环引用
- 智能指针的死穴 -- 循环引用
- 智能指针的死穴 -- 循环引用
- 智能指针的死穴 -- 循环引用
- c++ 智能指针及 循环引用问题
- 智能指针的死穴 -- 循环引用
- c++ 智能指针及 循环引用问题
- c++ 智能指针及 循环引用问题
- 智能指针和弱引用
- 智能指针和弱引用
- 智能指针和弱引用
- Lua5.3.4测试代码
- 深入理解 Spring 事务原理
- 周末记录--2017.10.29
- H
- 【util】MappedByteBuffer按行读取的方案
- 智能指针与弱指针解决循环引用
- 浅谈SQL Server中的三种物理连接操作
- Collections 源码分析
- 第七篇 elasticsearch如何解决并发冲突问题
- 【笔记】树的表示与实现
- p60-4.3(4)
- ZOJ 1078
- 神经网络 数字可视化
- python 重建二叉树的三个方法