本文中的shared_ptr以vs2010中的std::tr1::shared_ptr作为研究对象。可能和boost中的有些许差异,特此说明。
shared_ptr提供了一个管理内存的简单有效的方法。shared_ptr能在以下方面给开发提供便利:
1、 使用shared_ptr能有效的解决忘记释放内存带来的内存泄漏问题。同时通过自定义删除器功能还能广泛的用于任何需要”释放”的资源管理。
2、 利用weak_ptr和shared_ptr搭配使用能解决一部分由于重复释放导致的野指针问题。
不谈拷贝构造的话,shared_ptr的基本构造方式有4种:
1、 无参数构造。
2、 传入一个指针构造一个shared_ptr。例如shared_ptr<Foo> f(new Foo)。
3、 传入一个指针和一个删除器构造一个shared_ptr。例子见后文。
4、 传入一个指针、一个删除器以及一个allocator构造一个shared_ptr。
当然还有一些其他的,例如从auto_ptr从weak_ptr从null_ptr构造。
另外,类似于void*,shared_ptr<void>可以容纳任何类型的指针。
l use_count()方法。获取到当前智能指针的引用计数。0表示没有任何地方引用。
l get()方法。获取到raw指针。
l reset()方法。重新设置智能指针指向的对象,引用计数重新设置为1。reset方法的函数原型有4种,基本上和前文提到的4种构造函数一一对应。
l swap()方法。交换两个智能指针的内容。
l operater =()方法。该方法会涉及到引用计数的增加。
智能指针能够自定义删除器是一个很重要的功能,该功能使得能够跨dll传递shared_ptr变为可能(当然前提是多个dll使用的shared_ptr实现要一样)。尤其是当c++11的Lambda表达式出现后这个功能用起来更加方便。
先来看自定义删除器的构造方法:
template<class _Ux,
class _Dx>
shared_ptr(_Ux *_Px, _Dx _Dt)
{ // construct with _Px, deleter
_Resetp(_Px, _Dt);
}
其中构造函数的第二个参数就是删除器。这里要求删除器:
1、 是”可调用”的即可,例如function object、函数指针、Lambda表达式、bind/functor等等均可。
2、 返回值是void,参数是Ux*
3、 从形参看出,删除器以传值的方式传入,所以要求删除器要是可拷贝的,否则会编译出错。
4、 删除器不要抛出异常。
例如:
shared_ptr<Foo> shot1(new Foo(1),[&](Foo* p){p->Release();});
boost或者stl都提供了make_shared这个函数。用来方便的创建shared_ptr。
make_shared的好处有两点:
1、 既然用了shared_ptr不用手动delete指针,那么最好也不要在代码中出现new。make_shared正是在函数内封装了new的操作。
2、 从shared_ptr的数据接口了解到,在构造shared_ptr的时候,会new出一个对象保存指针的相关信息。所以一般来说,shared_ptr<Foo> x(new Foo); 需要为Foo 和ref_count 各分配一次内存。如果使用make_shared来创建的话,make_shared内部会尽量将两次内存分配在连续的位置(这个得看用的什么heap管理)。这里理论上能够更快一些。
说下缺点:
1、 make_shared只能针对new出来的,对于使用工厂创建出来的对象无能为力。
2、 需要定制删除器时,make_shared无能为力。
3、 make_shared目前只支持10个参数
另外,make_shared代码很有意思,为了方便的定义10个参数,宏定义用得鬼斧神工。
如果只能指针声明为基类的指针,指向的实际类型是子类的话,shared_ptr会自动完成。其他的转型一眼就能看明白,无需多言:
tr1::const_pointer_cast
tr1::dynamic_pointer_cast
tr1::static_pointer_cast
使用shared_ptr的目的就是管理对象的生命周期。在使用了shared_ptr以后有几个事情会变得和以往不太一样。
首先,用了shared_ptr就表明对象是使用引用计数来管理,那么该对象什么时候真正被从内存中释放掉就不是很明显了。比如说,可能你的代码中持有了一份shared_ptr的拷贝,就会导致某个对象一直存留下来。
发生这样的事情后,最好的下场是:后释放的shared_ptr在析构的时候吐核。
在实际编码中要注意。不要把一个raw指针交给多个shared_ptr管理。发生这样的事情很可能是在遗留代码上使用新特性导致的。
例如这样的例子:
class Foo
{
public:
Foo* GetThis()
{
return this;
}
}
要把这样的代码改为返回shared_ptr<Foo>,不那么好改。假如直接这样修改会有严重的问题:
shared_ptr<Foo> GetThis()
{
return shared_ptr<Foo>(this);
}
因为shared_ptr<Foo>被使用完后就析构了,引用计数减到0以后就会把this delete掉。照成野指针。
为了解决这个问题,标准库提供了一个方法:让类派生自一个模板类:enable_shared_from_this<T>。然后调用shared_from_this()函数即可。
class Foo: public enable_shared_from_this<Foo>
{
public:
shared_ptr<Foo> GetThis()
{
return shared_from_this();;
}
}
这个方法看上去不那么美观,但是确实解决了一些问题。也带来了另一些问题:shared_from_this()这个函数不能够在构造函数中调用。具体原理下一篇文章剖析shared_ptr实现原理时再讲吧。
shared_ptr的线程安全的定义在boost的文档中有明确的说明:
l 一个shared_ptr对象可以被多个线程同时read
l 两个shared_ptr对象,指向同一个raw指针,两个个线程分别write这两个shared_ptr对象,是安全的。包括析构。
l 多个线程如果要对同一个shared_ptr对象读写,是线程不安全的
也就是说,唯一需要注意的就是:多个线程中对同一个shared_ptr对象读写时需要加锁。但是即使是加锁也有技巧。比较好的方式是:
thread.lock();
shared_ptr tmpPtr=globalSharedPtr; // globalSharedPtr是多个线程读写的那个
thread.unlock();
后面的操作均针对tmpPtr进行
…
环形引用是指这样的情况:
Class A的一个实例中持有一个shared_ptr<B>,Class B的一个实例中持有shared_ptr<A>。考虑以下代码:
class CParent
{
public:
shared_ptr< CChild > children;
};
class CChild
{
public:
shared_ptr< CParent > parent;
};
int main()
{
{
shared_ptr< CParent > pA(new CParent);
shared_ptr< CChild > pB(new CChild);
pA-> children =pB;
pB-> parent =pA;
}
//到这里pA和pB都未能被释放掉
}
要解决环形引用,没有特别好的办法。在分析代码以后,知道了在某个地方可能有环形引用,那么可以使用weak_ptr来替代shared_ptr。
weak_ptr本身不具有指针的行为,例如你不能对一个weak_ptr来进行*或者->操作。它通常用来和shared_ptr配合使用。
weak_ptr作为一个”shared_ptr的观察者”能够获知shared_ptr的引用计数,还可以获知一个shared_ptr是否已经被析构了。单冲这一点来说,就一点不weak了。
有两种方法可以构造一个weak_ptr
1、 从shared_ptr构造而来。这种情况不会增加shared_ptr的引用计数。当然会增加另一个计数,这个放到下一篇中讲。
2、 从另一个weak_ptr拷贝。
也就是说weak_ptr不可能脱离shared_ptr而存在。
返回布尔,当返回true的时候表示,weak_ptr关联的shared_ptr已经被析构了。
int _tmain(int argc, _TCHAR* argv[])
{
shared_ptr<foo> fptr=shared_ptr<foo>(new foo(1,2));
weak_ptr<foo> wptr=fptr;
fptr.reset();
if(wptr.expired())
{
cout<<”wptr has expired”<<endl;
}
system(“pause”);
return 0;
}
从当前的weak_ptr创建一个新的shared_ptr。如果此时expired()返回true时,创建的shared_ptr中将保存一个null_ptr。
返回当前关联的shared_ptr的引用计数是多少。expired()返回true时,该函数返回0。
weak_ptr的特性是:weak_ptr不会增加shared_ptr的引用计数,所以weak_ptr通常用来解决shared_ptr无法解决的问题,例如环形引用。weak_ptr常见的使用场景有这么几个:
1、 想管理某些资源,但是又不想增加引用计数,那么就可以保存weak_ptr。
2、 当知道了有环形引用后,可以使用weak_ptr。例如上面的例子可以改为这样:
class CParent
{
public:
shared_ptr< CChild > children;
};
class CChild
{
public:
weak_ptr< CParent > parent;
};
int main()
{
{
shared_ptr< CParent > pA(new CParent);
shared_ptr< CChild > pB(new CChild);
pA-> children =pB;
pB-> parent =pA;
}
}
3、 某些情况下,需要知道某个shared_ptr是否已经释放了。
1、 在遗留代码上如果要引入shared_ptr要谨慎!shared_ptr带来的不确定性可能要比带来的便利性大的多。
2、 使用shared_ptr并不是意味着能偷懒。反而你更需要了解用shared_ptr管理的对象的生命周期应该是什么样子的,是不是有环形引用,是不是有线程安全问题,是不是会在某个地方意外的被某个东西hold住了。
3、 一个对象如何使用shared_ptr管理那么最好全部使用shared_ptr来管理,必要的时候可以使用weak_ptr。千万不要raw ptr和智能指针混用
3、 不要以传递指针的形式传递shared_ptr。
4、 多线程读写同一个shared_ptr的时候,可以先加锁拷贝一份出来,然后解锁即可。
1、 1、《Boost程序库完全开发指南》
2、 当析构函数遇到多线程──C++ 中线程安全的对象回调
http://www.cnblogs.com/Solstice/archive/2010/02/10/dtor_meets_threads.html
3、 为什么多线程读写shared_ptr 要加锁
http://www.cnblogs.com/Solstice/archive/2013/01/28/2879366.html
4、 vc stl
————————以上文章转自:shared_ptr简介以及常见问题