智能指针与弱指针解决循环引用
2017-10-29 22:31
447 查看
当我们学会使用智能指针的时候,会发现他有很多好处,但库里面提供的智能指针shared_ptr也并不是万能的,他会存在循环引用的问题,下面我们就通过实例来具体分析循环引用这种情况。
在这个例子中,我们自定义了一个节点类型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不能单独使用管理空间,我们将代码重写如下。
两个节点成功的被销毁,下面我们来看看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的引用计数空间;
就这样,四块空间被销毁,避免了循环引用带来的内存泄漏问题。
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的引用计数空间;
就这样,四块空间被销毁,避免了循环引用带来的内存泄漏问题。
相关文章推荐
- C++智能指针(三):weak_ptr--解决shared_ptr循环引用问题
- C++智能指针循环引用解决
- [zz] C++智能指针循环引用解决
- 【C++】智能指针简述(五):解决循环引用的weak_ptr
- 智能指针的循环引用和如何解决循环引用
- 智能指针的模拟实现shared_ptr 循环引用 定置删除器
- 智能指针的死穴 -- 循环引用
- 智能指针循环引用--转
- c++ 智能指针及 循环引用问题
- 智能指针的死穴 -- 循环引用
- c++智能指针循环引用带来的问题及解决方案
- 关于c++ 智能指针及 循环引用的问题
- c++:分析智能指针shared_ptr存在的循环引用的缺陷
- c++ 智能指针及 循环引用问题
- 智能指针weak_ptr解决循环依赖问题
- 智能指针 -- 循环引用
- 智能指针的交叉引用问题及解决方法
- 智能指针(下)-----boost库智能指针,定制删除器、循环引用
- 关于boost 库 shared_ptr 智能指针的循环引用【2013.10.22】
- 智能指针的死穴 -- 循环引用