智能指针在boost中很早就有了,在tr1上也很早,但是没怎么用,后来0x标准出来之后,智能指针变成了标准库,所以现在用起来就不区分boost和std了。
主要说下share_ptr的几个注意点,待补全。
1.环状的链式结构可能会形成内存泄露
例如:
class BaseClass; class ChildClass; typedef std::shared_ptrBaseClassPtr; typedef std::shared_ptr ChildClassPtr; class BaseClass { public: ChildClassPtr childClass; protected: private: }; class ChildClass { public: BaseClassPtr baseClass; protected: private: }; int _tmain(int argc, _TCHAR* argv[]) { BaseClassPtr base(new BaseClass()); ChildClassPtr child(new ChildClass()); base->childClass = child; child->baseClass = base; system("pause"); return 0; }
2.多线程环境下使用代价更大。
因为share_ptr内部有两个数据成员,一个是指向对象的指针 ptr,另一个是 ref_count 指针,指向堆上的 ref_count 对象,读写操作不能原子化,(具体的结构图可以查看 陈硕的文章《为什么多线程读写 shared_ptr 要加锁?》)所以多线程下要么加锁,要么小心翼翼使用share_ptr。
例如:
class Test { public: Test() {} ~Test() {} // ... protected: private: }; void func(std::shared_ptr test_ptr) { // 大量使用test_ptr std::shared_ptr temp_ptr = test_ptr; } int _tmain(int argc, _TCHAR* argv[]) { std::shared_ptr sp(new Test()); boost::thread th1(std::bind(&func, sp)); boost::thread th2(std::bind(&func, sp)); th1.join(); th2.join(); return 0; }
上面的代码不知道什么时候可能就宕了,而且不容易找到问题,这个时候你就得硬看代码了。
你也可以通过使用weak_ptr来解决这个问题,例如上述例子可以修改为:
class Test { public: Test() {} ~Test() {} // ... protected: private: }; void func(std::weak_ptr test_ptr) { // 大量使用test_ptr std::weak_ptr temp_ptr = test_ptr; } int _tmain(int argc, _TCHAR* argv[]) { std::shared_ptr sp(new Test()); std::weak_ptr wp(sp); boost::thread th1(std::bind(&func, wp)); boost::thread th2(std::bind(&func, wp)); th1.join(); th2.join(); return 0; }
weak_ptr是一种可构造可赋值的不增加引用计数来管理share_ptr的智能指针,它可以非常方便的通过weak_ptr.lock()转为share_ptr,通过weak_ptr.expired()来判断智能指针是否被释放,还是非常方便的。条目1中的例子使用weak_ptr就可以解决问题
3.share_ptr包装this的时候使用enable_shared_from_this
class Test { public: Test() {} ~Test() {} std::shared_ptr get_ptr() { return std::shared_ptr(this); } // ... protected: private: }; int _tmain(int argc, _TCHAR* argv[]) { Test t; std::shared_ptr t_ptr(t.get_ptr()); return 0; }
这样就会发生析构两次的问题,可以使用enable_shared_from_this来做共享,上面例子修改为
class Test : public std::enable_shared_from_this{ public: Test() {} ~Test() {} std::shared_ptr get_ptr() { return shared_from_this(); } // ... protected: private: }; int _tmain(int argc, _TCHAR* argv[]) { std::shared_ptr t_ptr(new Test()); t_ptr->get_ptr(); return 0; }
4.share_ptr多次引用同一数据会导致内存多次释放
int _tmain(int argc, _TCHAR* argv[]) { int* int_ptr = new int[100]; std::shared_ptr s_int_ptr1(int_ptr); // do something std::shared_ptr s_int_ptr2(int_ptr); return 0; }
而且C++之父对share_ptr的初衷是:“shared_ptr用于表示共享拥有权。然而共享拥有权并不是我的初衷。在我看来,一个更好的办法是为对象指明拥有者并且为对象定义一个可以预测的生存范围。”
对倒数第二种情况在一步的分析:
可以说,shared_ptr着力解决类对象一级的资源管理,至于类对象内部,shared_ptr暂时还无法管理;那么这是否会出现问题呢?来看看这样的代码:
为了使类似Point::Add()::Add()可以连续进行Add操作成为可能,Point1定义了Add方法,并返回了this指针(从Effective C++的条款看,这里最好该以传值形式返回临时变量,在此为了说明问题,暂且不考虑这种设计是否合理,但他就这样存在了)。在TestPoint1Add()函数中,使用此返回的指针重置了p2,这样p2和p1就同时管理了同一个对象,但是他们却互相不知道这事儿,于是悲剧发生了。在作用域结束的时候,他们两个都去对所管理的资源进行析构,从而出现了上述的输出。从最后一行输出也可以看出,所管理的资源,已经处于“无效”的状态了。
那么,我们是否可以改变一下呢,让Add返回一个shared_ptr了呢。我们来看看Point2:
针对这种情况,shared_ptr的解决方案是: enable_shared_from_this这个模版类。所有需要在内部传递this指针的类,都从enable_shared_from_this继承;在需要传递this的时候,使用其成员函数shared_from_this()来返回一个shared_ptr。运用这种方案,我们改良我们的Point类,得到如下的Point3:
从这个输出可以看出,在这里的对象析构已经变得正常。因此,在类内部需要传递this的场景下,enable_shared_from_this是一个比较靠谱的方案;只不过,要谨慎的记住,使用该方案的一个前提,就是类的对象已经被shared_ptr管理,否则,就等着抛异常吧。例如:
于是,shared_ptr又引入了注意事项:
好嘛,到最后,再做一个总结:
上述情况出现的场合:
使用boost库时,经常会看到如下的类
class A:public enable_share_from_this
在什么情况下要使类A继承enable_share_from_this?
使用场合:当类A被share_ptr管理,且在类A的成员函数里需要把当前类对象作为参数传给其他函数时,就需要传递一个指向自身的share_ptr。
我们就使类A继承enable_share_from_this,然后通过其成员函数share_from_this()返回当指向自身的share_ptr。
以上有2个疑惑:
1.把当前类对象作为参数传给其他函数时,为什么要传递share_ptr呢?直接传递this指针不可以吗?
一个裸指针传递给调用者,谁也不知道调用者会干什么?假如调用者delete了该对象,而share_tr此时还指向该对象。
2.这样传递share_ptr可以吗?share_ptr
这样会造成2个非共享的share_ptr指向一个对象,最后造成2次析构该对象。
实现原理:
share_from_this()是如何返回指向该对象的share_ptr的?显然它不能直接return share_ptr