1:不要把一个原生指针给多个shared_ptr管理
int* ptr = new int;
shared_ptr<int> p1(ptr);
shared_ptr<int> p2(ptr); //logic error
- ptr对象被删除了2次
- 这种问题比喻成“二龙治水”,在原生指针中也同样可能发生。
2:不要把this指针给shared_ptr
#include <memory>
#include <iostream>
class Bad
{
public:
std::shared_ptr<Bad> getptr() {
return std::shared_ptr<Bad>(this);
}
~Bad() { std::cout << "Bad::~Bad() called" << std::endl; }
};
int main()
{
// 错误的示例,每个shared_ptr都认为自己是对象仅有的所有者
std::shared_ptr<Bad> bp1(new Bad());
std::shared_ptr<Bad> bp2 = bp1->getptr();
// 打印bp1和bp2的引用计数
std::cout << "bp1.use_count() = " << bp1.use_count() << std::endl;
std::cout << "bp2.use_count() = " << bp2.use_count() << std::endl;
} // Bad 对象将会被删除两次
//bp1.use_count() = 1
//bp2.use_count() = 1
//Bad::~Bad() called
//Bad::~Bad() called
正确实现
#include <memory>
#include <iostream>
struct Good : std::enable_shared_from_this<Good> // 注意:继承
{
public:
std::shared_ptr<Good> getptr() {
return shared_from_this();
}
~Good() { std::cout << "Good::~Good() called" << std::endl; }
};
int main()
{
// 大括号用于限制作用域,这样智能指针就能在system("pause")之前析构
{
std::shared_ptr<Good> gp1(new Good());
std::shared_ptr<Good> gp2 = gp1->getptr();
// 打印gp1和gp2的引用计数
std::cout << "gp1.use_count() = " << gp1.use_count() << std::endl;
std::cout << "gp2.use_count() = " << gp2.use_count() << std::endl;
}
system("pause");
}
为何会出现这种使用场合
- 因为在异步调用中,存在一个保活机制,异步函数执行的时间点我们是无法确定的,然而异步函数可能会使用到异步调用之前就存在的变量。
- 为了保证该变量在异步函数执期间一直有效,我们可以传递一个指向自身的share_ptr给异步函数,这样在异步函数执行期间share_ptr所管理的对象就不会析构,所使用的变量也会一直有效了(保活)。
3:shared_ptr作为被保护的对象的成员时,小心因循环引用造成无法释放资源。
-
对象需要相互协作,对象A需要知道对象B的地址,这样才能给对象B发消息(或调用其方法)。
-
设计模式中有大量例子,一个对象中有其他对象的指针。
-
现在把原生指针替换为shared_ptr.
-
假设a对象中含有一个shared_ptr指向b对象;
-
假设b对象中含有一个shared_ptr指向a对象
-
并且a,b对象都是堆中分配的。很轻易就能与他们失去最后联系。
-
考虑某个shared_ptr local_a;是我们能最后一个看到a对象的共享智能指针,其use_count==2,
-
因为对象b中持有a的指针。所以当local_a说再见时,local_a只是把a对象的use_count改成1。
-
同理b对象。然后我们再也看不到a,b的影子了,他们就静静的躺在堆里,成为断线的风筝。
-
解决方案是:Use weak_ptr to “break cycles."(boost文档里写的)或者显示的清理
4:不要在函数实参里创建shared_ptr
function ( shared_ptr<int>(new int), g( ) ); //有缺陷
- 可能的过程是先new int,
- 然后调g( ),g( )发生异常,shared_ptr没有创建,int内存泄露
shared_ptr<int> p(new int());
f(p, g()); //Boost推荐写法
5:对象内部生成shared_ptr
前面说过,不能把this指针直接扔给shared_ptr. 但是没有禁止在对象内部生成自己的shared_ptr
//这是Boost的例子改的。
class Y: public boost::enable_shared_from_this<Y>
{
boost::shared_ptr<Y> GetSelf()
{
return shared_from_this();
}
};
原理是这样的。
- 普通的(没有继承enable_shared_from_this)类T的shared_ptr p(new T).
- p作为栈对象占8个字节,为了记录(new T)对象的引用计数,p会在堆上分配16个字节以保存
- 引用计数等“智能信息”。
- share_ptr没有“嵌入(intrusive)”到T对象,或者说T对象对share_ptr毫不知情。
- Y对象则不同,Y对象已经被“嵌入”了一些share_ptr相关的信息,目的是为了找到“全局性”的那16字节的本对象的“智能信息”。
原理说完了,就是陷阱
Y y;
boost::shared_ptr<Y> p= y.GetSelf(); //无知的代码,y根本就不是new出来的
Y* y = new Y;
boost::shared_ptr<Y> p= y->GetSelf(); //似是而非,仍旧程序崩盘。
Boost文档说,在调用shared_from_this()之前,必须存在一个正常途径创建的shared_ptr
boost::shared_ptr<Y> spy(new Y)
boost::shared_ptr<Y> p = spy->GetSelf(); //OK
6 :处理不是new的对象要小心。
int* pi = (int*)malloc(4)
shared_ptr<int> sp( pi ) ; //delete马嘴不对malloc驴头。
7:多线程对引用计数的影响。
- 如果是轻量级的锁,比如InterLockIncrement等,对程序影响不大
- 如果是重量级的锁,就要考虑因为share_ptr维护引用计数而造成的上下文切换开销。
- 1.33版本以后的shared_ptr对引用计数的操作使用的是Lock-Free(类似InterLockIncrement函数族)的操作,应该效率不错,而且能保证线程安全(库必须保证其安全,程序员都没有干预这些隐藏事物的机会)
- Boost文档说read,write同时对shared_ptr操作时,行为不确定。这是因为shared_ptr本身有两个成员px,pi。
- 多线程同时对px读写是要出问题的。与一个int的全局变量多线程读写会出问题的原因一样。
8:对象数组用shared_array
int* pint = new int[100];
shared_array<int> p (pint );
既然shared_ptr对应着delete;显然需要一个delete[]对应物shared_array
9:学会用删除器
struct Test_Deleter
{
void operator ()( Test* p){ ::free(p); }
};
Test* t = (Test*)malloc(sizeof(Test));
new (t) Test;
shared_ptr<Test> sp( t , Test_Deleter() ); //删除器可以改变share_ptr销毁对象行为
有了删除器,shared_array无用武之地了。
template<class T>
struct Array_Deleter
{
void operator ()( T*){ delete[] p; }
};
int* pint = new int[100];
shared_ptr<int> p (pint, Array_Deleter<int>() );
10:学会用分配器
- 存放引用计数的地方是堆内存,需要16-20字节的开销。
- 如果大量使用shared_ptr会造成大量内存碎片。
- shared_ptr构造函数的第3个参数是分配器,可以解决这个问题。
- shared_ptr p( (new Test), Test_Deleter(), Mallocator() );
- 注意删除器Test_Deleter是针对Test类的。分配器是针对shared_ptr内部数据的。
//Mallocator<Test>()是个临时对象(无状态的),符合STL分配器规约。
template <typename T>
class Mallocator {
//略。。。。。。
T * allocate(const size_t n) const {
return singleton_pool<T,sizeof(T)>::malloc();
}
//略。。。。。。
//Mallocator传入Test,实际分配的类型确是
class boost::detail::sp_counted_impl_pda<class Test *,
struct Test_Deleter,
class Mallocator<class Test> >
//这是用typeid(T).name()打印出来的。可能和rebind相关。
11 weak_ptr在使用前需要检查合法性。
weak_ptr<K> wp;
{
shared_ptr<K> sp(new K); //sp.use_count()==1
wp = sp; //wp不会改变引用计数,所以sp.use_count()==1
shared_ptr<K> sp_ok = wp.lock(); //wp没有重载->操作符。只能这样取所指向的对象
}
shared_ptr<K> sp_null = wp.lock(); //sp_null .use_count()==0;
- 因为上述代码中sp和sp_ok离开了作用域,其容纳的K对象已经被释放了。
- 得到了一个容纳NULL指针的sp_null对象。在使用wp前需要调用wp.expired()函数判断一下。
- 因为wp还仍旧存在,虽然引用计数等于0,仍有某处“全局”性的存储块保存着这个计数信息。
- 直到最后一个weak_ptr对象被析构,这块“堆”存储块才能被回收。否则weak_ptr无法直到自己
- 所容纳的那个指针资源的当前状态。
12 不要new shared_ptr< T >
- 本来shared_ptr就是为了管理指针资源的,不要又引入一个需要管理的指针资源shared_ptr*
13 尽量不要get
class B{...};
class D : public B{ ...}; //继承层次关系
shared_ptr<B> sp (new D); //通过隐式转换,储存D的指针。
B* b = sp.get(); //shared_ptr辛辛苦苦隐藏的原生指针就这么被刨出来了。
D* d = dynamic_cast<D*>(b); //这是使用get的正当理由吗?
正确的做法
shared_ptr<B> spb (new D) ;
shared_ptr<D> spd = shared_dynamic_cast<D>(spb); //变成子类的指针
shared_ptr在竭尽全力表演的像一个原生指针,原生指针能干的事,它也基本上能干。
另一个同get相关的错误
shared_ptr<T> sp(new T);
shared_ptr<T> sp2( sp.get() ) ;//又一个“二龙治水”实例,指针会删2次而错误。
14 不要memcpy shared_ptr
shared_ptr<B> sp1 (new B) ;
shared_ptr<B> sp2;
memcpy(&sp2,&sp1,sizeof(shared_ptr<B>)); //sp2.use_count()==1
很显然,不是通过正常途径(拷贝构造,赋值运算),引用计数是不会正确增长的。
15 使用BOOST预定义的宏去改变shared_ptr行为。
- shared_ptr行为由类似BOOST_SP_DISABLE_THREADS这样的宏控制。
- 需要去学习他们到底是干什么的。
- 大师Andrei Alexandrescu设计了一种基于模板策略设计模式的智能指针,通过几个模板参数去定制化智能指针的行为。
- Boost却不以为然,官方解释是:需要统一的接口,这样利于大规模书写。
- smart_ptr<T,OwnershipPolicy,ConversionPolicy,CheckingPolicy,StoragePolicy> sp(new T);
- 上述接口缺点是外形复杂,看上去像个大花脸。
- 优点是客户程序员可以轻易的定制行为。
17 构造函数里调用shared_from_this抛例外
class Holder:public enable_shared_from_this<Holder>{
public:
Holder() {
shared_ptr<Holder> sp = shared_from_this();
int x = sp.use_count();
}
};
同前面条款5,不符合enable_shared_from_this使用前提。