Shared_ptr详解
来源:互联网 发布:山东临沂外包淘宝客服 编辑:程序博客网 时间:2024/06/07 13:09
shared_ptr在boost库中已经有多年了,C++11又为其正名,把他引入了STL库,放到了std的下面,可见其颇有用武之地;但是shared_ptr是万能的吗?有没有什么样的问题呢?本文并不说明shared_ptr的设计原理,也不是为了说明如何使用,只说一下在使用过程中的几点注意事项。
用法
shared_ptr<int> sp(new int(10)); //一个指向整数的shared_ptr assert(sp.unique()); //现在shared_ptr是指针的唯一持有者 shared_ptr<int> sp2 = sp; //第二个shared_ptr,拷贝构造函数 assert(sp == sp2 && sp.use_count() == 2); //两个shared_ptr相等,指向同一个对象,引用计数为2 *sp2 = 100; //使用解引用操作符修改被指对象 assert(*sp == 100); //另一个shared_ptr也同时被修改 sp.reset(); //停止shared_ptr的使用 assert(!sp); //sp不再持有任何指针(空指针)
shared_ptr的线程安全性
shared_ptr本身不是100%线程安全的,它的引用计数本身是安全且无锁的,但对象的读写则不是,因为shared_ptr有两个数据成员,读写操作不能原子化。根据文档,shared_ptr的线程安全级别和内建类型、标准容器、string一样,即(参考智能指针线程相关):
- 一个shared_ptr 实体可被多个线程同时读取;
- 两个shared_ptr 实体可以被两个线程同时写入,“析构”算写操作;
- 如果要从多个线程读写同一个shared_ptr对象,那么需要加锁;
智能指针是万能良药?
智能指针为解决资源泄漏,编写异常安全代码提供了一种解决方案,但它是万能良药吗?使用智能指针,就能保证不会再有资源泄漏吗?来看下面的代码:
//header filevoid func( shared_ptr<T1> ptr1, shared ptr<T2> ptr2 );//call func like thisfunc( shared_ptr<T1>( new T1() ), shared_ptr<T2>( new T2() ) );
上面的函数调用,看起来是安全的,但在现实世界中,其实不然:由于C++并未定义一个表达式的求值顺序,因此上述函数调用除了func在最后得到调用之外可以确定,其他的执行序列则很可能被拆分成如下步骤:
a.分配内存给T1b.构造T1对象c.分配内存给T2 d.构造T2对象 e.构造T1的智能指针对象 f.构造T2的智能指针对象 g.调用func
或者:
a1.分配内存给T1b1.分配内存给T2c1.构造T1对象 d1.构造T2对象 e1.构造T1的智能指针对象 f1.构造T2的智能指针对象 g1.调用func
上述无论哪种形式的构造序列,如果在 c 或者 d / c1 或者d1失败,则T1对象分配内存必然泄漏。
为了解决这个问题,有一个依然使用智能指针的笨办法:
template<class T>shared_ptr<T> shared_ptr_new(){ return shared_ptr<T>( new T );}//call like thisfunc( shared_ptr_new<T1>(), shared_ptr_new<T2>() );
使用这种办法,可以解决因为产生异常导致资源泄漏的问题,然而另一个问题又出现了,如果T1或者T2的构造函数需要提供参数该怎么办呢?难道提供很多个重载的版本?——>可以倒是可以,只要你不嫌累,而且有足够的先见性。
其实,最完美的方案,其实也是最简单的,就是尽量简单的书写代码,像这样:
//header filevoid func( shared_ptr<T1> ptr1, shared_ptr<T2> ptr2 );//call func like thisshared_ptr<T1> ptr1( new T1() );shared_ptr<T2> ptr2( new T2() );func(ptr1, ptr2 );
这样简简单单的代码,避免了异常导致的泄漏。又应了那句话:简单就是美。其实,在一个表达式中,分配多个资源或者需要求多个值等操作都是不安全的。
归总为一句话:抛弃临时对象,让所有的智能指针都有名字,就可以避免此类问题的发生。
shared_ptr交叉引用导致的泄漏
是否让每个智能指针都有了名字,就不会再有内存泄漏?不一定,看下面的代码的输出,是否会感到惊讶。。。
class CLeader;class CMember;class CLeader{public: CLeader() { cout << "CLeader::CLeader()" << endl; } ~CLeader() { cout << "CLeader:;~CLeader() " << endl; } std::shared_ptr<CMember> member;};class CMember{public: CMember() { cout << "CMember::CMember()" << endl; } ~CMember() { cout << "CMember::~CMember() " << endl; } std::shared_ptr<CLeader> leader; };void TestSharedPtrCrossReference(){ cout << "TestCrossReference<<<" << endl; boost::shared_ptr<CLeader> ptrleader( new CLeader ); boost::shared_ptr<CMember> ptrmember( new CMember ); ptrleader->member = ptrmember; ptrmember->leader = ptrleader; cout <<" ptrleader.use_count: " << ptrleader.use_count() << endl; cout <<" ptrmember.use_count: " << ptrmember.use_count() << endl;}//output:CLeader::CLeader()CMember::CMember() ptrleader.use_count: 2 ptrmember.use_count: 2
从运行结果来看,两个对象的析构函数都没有调用,也就是出现了内存泄漏—–>原因在于:TestSharedPtrCrossReference() 函数退出时,两个shared_ptr对象的引用计数都是2,所以不会释放对象;
这里出现了常见的交叉引用的问题,这个问题,即使用原生指针互相记录时也需要格外小心。shared_ptr在这里栽了跟头,ptrleader和ptrmember在离开作用域的时候,由于引用计数不为1,所以最后一次的release操作(shared_ptr析构函数里面调用)也无法destroy掉所托管的资源。
为了解决这个问题,可以采用weak_ptr来隔断交叉引用的回路,所谓的weak_ptr,是一种弱引用,表示只是对某个对象的一个引用和使用,而不做管理工作。weak_ptr 和 shared_ptr对比:
只要有一个强引用存在,强引用对象就不能释放; 是对象存在的一个引用;
即使有弱引用存在,对象仍然可以释放; 增加对象的引用计数 不增加对象的引用计数 负责资源管理,在引用计数为0时释放资源 不负责资源管理 有多个构造函数,可以从任意类型初始化 只能从一个shared_ptr或者weak_ptr对象上进行初始化 zebra stripes 行为类似原生指针,不过可以用expired()判断对象是否已经释放
由于weak_ptr具有上述的一些性质,所以如果把CMember的声明改成如下形式,就可以解除这种循环,从而每个资源都可以顺利释放。
class CMember{public: CMember() { cout << "CMember::CMember()" << endl; } ~CMember() { cout << "CMember::~CMember() " << endl; } boost::weak_ptr<CLeader> leader; };
这种使用weak_ptr的方式,是基于已暴露问题的修正方案,在做设计的时候,一般很难注意到这一点,总之,C++缺少垃圾收集机制,虽然智能指针提供了一个解决方案,但它也难以到达完美,因此,C++的资源管理必须慎之又慎。
类向外传递this 与 shared_ptr
可以说,shared_ptr着力解决类对象一级的资源管理,至于类对象内部,shared_ptr暂时还无法管理,那么这是否会出现问题呢?看如下代码:
class Point1{public: Point1() : X(0), Y(0) { cout << "Point1::Point1(), (" << X << "," << Y << ")" << endl; } Point1(int x, int y) : X(x), Y(y) { cout << "Point1::Point1(int x, int y), (" << X << "," << Y << ")" << endl; } ~Point1() { cout << "Point1::~Point1(), (" << X << "," << Y << ")" << endl; }public: Point1* Add(const Point1* rhs) { X += rhs->X; Y += rhs->Y; return this;}private: int X; int Y;};void TestPoint1Add(){ cout << "TestPoint1Add() >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" << endl; shared_ptr<Point1> p1( new Point1(2,2) ); shared_ptr<Point1> p2( new Point1(3,3) ); p2.reset( p1->Add(p2.get()) );}输出为:TestPoint1Add() >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Point1::Point1(int x, int y), (2,2)Point1::Point1(int x, int y), (3,3)Point1::~Point1(), (3,3)Point1::~Point1(), (5,5)Point1::~Point1(), (5411568,5243076)
为了使类似Point::Add()可以连续进行Add操作成为可能,Point1定义了Add方法,并返回了this指针(从Effective C++的条款看,这里最好该以传值形式返回临时变量,在此为了说明问题,暂且不考虑这种设计是否合理,但他就这样存在了)。在TestPoint1Add()函数中,使用此返回的指针重置了p2,这样p2和p1就同时管理了同一个对象,但是他们却互相不知道这事儿,于是悲剧发生了。在作用域结束的时候,他们两个都去对所管理的资源进行析构,从而出现了上述的输出。从最后一行输出也可以看出,所管理的资源,已经处于“无效”的状态了。
那么,我们是否可以改变一下呢,让Add返回一个shared_ptr了呢。我们来看看Point2:
class Point2{public: Point2() : X(0), Y(0) { cout << "Point2::Point2(), (" << X << "," << Y << ")" << endl; } Point2(int x, int y) : X(x), Y(y) { cout << "Point2::Point2(int x, int y), (" << X << "," << Y << ")" << endl; } ~Point2() { cout << "Point2::~Point2(), (" << X << "," << Y << ")" << endl; }public: shared_ptr<Point2> Add(const Point2* rhs) { X += rhs->X; Y += rhs->Y; return shared_ptr<Point2>(this);}private: int X; int Y;};void TestPoint2Add(){ cout << endl << "TestPoint2Add() >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" << endl; shared_ptr<Point2> p1( new Point2(2,2) ); shared_ptr<Point2> p2( new Point2(3,3) ); p2.swap( p1->Add(p2.get()) );}输出为:TestPoint2Add() >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Point2::Point2(int x, int y), (2,2)Point2::Point2(int x, int y), (3,3)Point2::~Point2(), (3,3)Point2::~Point2(), (5,5)Point2::~Point2(), (3379952,3211460)
从输出来看,哪怕使用shared_ptr来作为Add函数的返回值,仍然无济于事;对象仍然被删除了两次;
针对这种情况,shared_ptr的解决方案是:enable_shared_from_this这个模板类,所有需要在内部传递this指针的类,都从enable_shared_from_this继承,在需要传递this的时候,使用其成员函数shared_from_this() 来返回一个shared_ptr,运用这种方案,我们改良我们的Point类,得到如下的Point3:
class Point3 : public enable_shared_from_this<Point3>{public: Point3() : X(0), Y(0) { cout << "Point3::Point3(), (" << X << "," << Y << ")" << endl; } Point3(int x, int y) : X(x), Y(y) { cout << "Point3::Point3(int x, int y), (" << X << "," << Y << ")" << endl; } ~Point3() { cout << "Point3::~Point3(), (" << X << "," << Y << ")" << endl; }public: shared_ptr<Point3> Add(const Point3* rhs) { X += rhs->X; Y += rhs->Y; return shared_from_this();}private: int X; int Y;};void TestPoint3Add(){ cout << endl << "TestPoint3Add() >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" << endl; shared_ptr<Point3> p1( new Point3(2,2) ); shared_ptr<Point3> p2( new Point3(3,3) ); p2.swap( p1->Add(p2.get()) );}输出为:TestPoint3Add() >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Point3::Point3(int x, int y), (2,2)Point3::Point3(int x, int y), (3,3)Point3::~Point3(), (3,3)Point3::~Point3(), (5,5)
从这个输出可以看出,在这里的对象析构已经变得正常。因此,在类内部需要传递this的场景下,enable_shared_from_this是一个比较靠谱的方案,只不过,要谨慎的脊柱,使用该方案的一个前提,就是类的对象已经被shared_ptr管理,否则就等着抛异常吧。例如:
Point3 p1(10, 10);Point3 p2(20, 20);p1.Add( &p2 ); //此处抛异常
上面的代码会导致crash。那是因为p1没有被shared_ptr管理。之所以这样,是由于shared_ptr的构造函数才会去初始化enable_shared_from_this相关的引用计数(具体可以参考代码),所以如果对象没有被shared_ptr管理,shared_from_this()函数就会出错。
于是,shared_ptr又引入了注意事项:
- 若要在内部传递this,请考虑从enable_shared_from_this继承;
- 若从enable_shared_from_this继承,则类对象必须让shared_ptr接管;
- 如果要使用智能指针,那么就要保持一致,统统使用智能智能,尽量减少raw pointer裸指针的使用;
总结
- C++没有垃圾收集,资源管理需要自己来做;
- 智能指针可以部分解决资源管理的工作,但是不是万能的;
- 使用智能指针的时候,每个shared_ptr 对象都应该有一个名字,也就是避免在一个表达式内做多个资源的初始化;
- 避免shared_ptr的交叉引用,使用weak_ptr打破交叉引用;
- 使用enable_shared_from_this 机制来把this 从类内部传递出来;
- 资源管理保持统一风格,要么使用智能指针,要么就全部自己管理裸指针;
- shared_ptr详解
- shared_ptr详解
- 详解shared_ptr
- shared_ptr详解
- Shared_ptr详解
- shared_ptr 智能指针 详解
- shared_ptr
- shared_ptr
- shared_ptr
- shared_ptr
- shared_ptr
- shared_ptr
- shared_ptr
- shared_ptr
- shared_ptr
- shared_ptr
- shared_ptr
- shared_ptr
- Utis类
- 圣杯布局和双飞翼布局
- 挂断电话流程分析
- TensorFlow在windows环境下的安装(1)
- 试题库问题 最大流 输出方案
- Shared_ptr详解
- opencv学习心得
- 微信ios版本的两个灰度功能和一些小改变
- 2017-8-13
- javascript中的BOM
- 简单的旋转木马轮播
- 剑指Offer 含有Min函数的栈
- JAVA设计模式之工厂模式(简单工厂模式+工厂方法模式)
- codeforces 839 E. Mother of Dragons(最大团)