关于std::shared_ptr与std::enable_shared_from_this循环引用导致的问题
来源:互联网 发布:武汉大学网络 编辑:程序博客网 时间:2024/06/05 20:35
自从C++11有了std::shared_ptr这样的智能指针,作为C++程序只要将一个堆上的类对象用std::shared_ptr包裹一下就可以做到内存自动释放了。看一个例子:
#include "stdafx.h"#include <memory>class A{public: A() { m_i = 9; } ~A() { m_i = 0; }public: int m_i;};int _tmain(int argc, _TCHAR* argv[]){ { std::shared_ptr<A> spa(new A()); } return 0;}
但是假如,我们有一些开发需求中(也可能是前同事遗留下的代码),我们需要在一个类中引用自身,即一个类的一个成员变量是一个std::shared_ptr对象,它引用了类对象自身,这里分为两种情况,第一种情况是类对象是栈对象,第二种情况是类对象是堆对象。
我们先看类对象是栈对象的情形,示例代码如下:
#include "stdafx.h"#include <memory>class A : public std::enable_shared_from_this<A>{public: A() { m_i = 9; //注意: //比较好的做法是在构造函数里面调用shared_from_this()给m_SelfPtr赋值 //但是很遗憾不能这么做,如果写在构造函数里面程序会直接崩溃 } ~A() { m_i = 0; } void func() { m_SelfPtr = shared_from_this(); }public: int m_i; std::shared_ptr<A> m_SelfPtr;};int _tmain(int argc, _TCHAR* argv[]){ { A a; a.func(); } return 0;}
上面的代码,在调用a.func()时程序会直接崩溃,崩溃的原因是调用shared_from_this()函数里面,看一下崩溃的调用堆栈:
我们来看下崩溃的原因,看下shared_from_this()函数的调用细节:
也就是说shared_from_this()函数内部会先调用shared_ptr的构造函数去构造一个shared_ptr对象,参数是自己的成员变量_Wptr,这是一个std::weak_ptr:
private:template<class _Ty1,class _Ty2>friend void _Do_enable(_Ty1 *,enable_shared_from_this<_Ty2>*,_Ref_count_base *);mutable weak_ptr<_Ty> _Wptr;
而shared_ptr的构造函数里面又会调用reset()先释放之前的对象引用,如果这个之前的对象就是_Wptr这个指针去引用,但是现在_Wptr是空的,就抛出一个异常。_Wptr之所以为空,是这个指针引用的对象并没有被任何智能指针所包裹(A的对象a是栈变量)。这就是崩溃的原因。
我们来接着看下A对象是堆对象的情形:
class A : public std::enable_shared_from_this<A>{public: A() { m_i = 9; //注意: //比较好的做法是在构造函数里面调用shared_from_this()给m_SelfPtr赋值 //但是很遗憾不能这么做,如果写在构造函数里面程序会直接崩溃 } ~A() { m_i = 0; } void func() { m_SelfPtr = shared_from_this(); }public: int m_i; std::shared_ptr<A> m_SelfPtr;};int _tmain(int argc, _TCHAR* argv[]){ { std::shared_ptr<A> spa(new A()); spa->func(); } return 0;}
这次不会崩溃了,但是遗憾的是,这个new出来的A对象的堆内存再也不会释放了(当然程序退出靠操作系统回收不算)。为啥不会释放呢?我们来分析下原因:
要想堆上的A被释放,那么至少需要所有指向A的std::shared_ptr对象都不再引用A,但是A的成员变量只有在A自己被释放的时候才会不再引用A。反过来说,A的成员变量m_SelfPtr等着A对象本身释放,而A作为堆对象释放的条件是所有引用它的的std::shared_ptr释放。这就相互矛盾了。这种情形导致,这样的A对象永远不会被自动释放。我们使用std::weak_ptr来看看最终这个A的引用计数是多少:
#include "stdafx.h"#include <memory>class A : public std::enable_shared_from_this<A>{public: A() { m_i = 9; //注意: //比较好的做法是在构造函数里面调用shared_from_this()给m_SelfPtr赋值 //但是很遗憾不能这么做,如果写在构造函数里面程序会直接崩溃 } ~A() { m_i = 0; } void func() { m_SelfPtr = shared_from_this(); }public: int m_i; std::shared_ptr<A> m_SelfPtr;};int _tmain(int argc, _TCHAR* argv[]){ std::weak_ptr<A> spwa; { std::shared_ptr<A> spa(new A()); spa->func(); spwa = spa; } printf("spwa usecount: %d\n", spwa.use_count()); return 0;}
确实和我们分析的一样,这个堆上的A引用计数永远是A了,所以不会被释放了。
那有什么解决方案呢?
我们可以在增加一个成员函数,在不需要A时,主动释放这个智能指针的成员变量引用的对象:
#include "stdafx.h"#include <memory>class A : public std::enable_shared_from_this<A>{public: A() { m_i = 9; //注意: //比较好的做法是在构造函数里面调用shared_from_this()给m_SelfPtr赋值 //但是很遗憾不能这么做,如果写在构造函数里面程序会直接崩溃 } ~A() { m_i = 0; } void func() { m_SelfPtr = shared_from_this(); } void release() { m_SelfPtr.reset(); }public: int m_i; std::shared_ptr<A> m_SelfPtr;};int _tmain(int argc, _TCHAR* argv[]){ std::weak_ptr<A> spwa; { std::shared_ptr<A> spa(new A()); spa->func(); spa->release(); spwa = spa; } printf("spwa usecount: %d\n", spwa.use_count()); return 0;}
这样,程序就会自动调用A的析构函数来释放自己呢。但是!!!这样人为地增加一个release()函数相当于手工调用了delete,使用智能指针还有什么意义,我们还得人工管理内存释放。
综合下来,这种在对象内部引用自己的智能指针是一种非常不好的设计,个人觉得还是要杜绝这种错误的用法。
- 关于std::shared_ptr与std::enable_shared_from_this循环引用导致的问题
- std::shared_ptr 和 std::weak_ptr的用法以及引用计数的循环引用问题
- std::shared_ptr 和 std::weak_ptr的用法以及引用计数的循环引用问题
- std::shared_ptr 和 std::weak_ptr的用法以及引用计数的循环引用问题
- std::shared_ptr 和 std::weak_ptr的用法以及引用计数的循环引用问题
- std::tr1::shared_ptr、std::tr1::weak_ptr及std::tr1::enable_shared_from_this
- 关于std:auto_ptr std:shared_ptr std:unique_ptr
- C++学习 std::tr1::shared_ptr、std::tr1::weak_ptr及std::tr1::enable_shared_from_this
- std::shared_ptr 与普通指针的转换
- 引用计数智能指针std::tr1::shared_ptr与weak_ptr
- std::unique_ptr和std::shared_ptr的用法
- std::shared_ptr
- std::shared_ptr
- boost::shared_ptr与std::tr1::shared_ptr
- std::shared_ptr的巧妙应用
- 关于std::move与右值引用
- c++中关于智能指针std::tr1::shared_ptr的用法
- -std=gnu++11 导致的问题
- 清澄A1039. 欢乐的跳跃者
- Ruby on rails 安装教程
- Spring filter原理深入浅出
- linux发送邮件配置
- C语言经典算法(三)——求二进制中1的个数的五种方法
- 关于std::shared_ptr与std::enable_shared_from_this循环引用导致的问题
- Mac 下安装 java环境
- 分享下我用Retrofit + Rxjava请求后台数据时遇到的问题
- 小希的迷宫 简单并查集
- 博弈论常用结论
- python_fullstack-小知识点
- java动态加载类
- MyBatis的动态sql语句详解,foreach等
- java多线程