C++ 智能指针(shared_ptr/weak_ptr)原理分析
其主要的类关系如下所示(省略相关的类模板参数):
图1
从上面的类图可以清楚的看出shared_ptr内部含有一个指向被管理对象(managed object)T的指针以及一个__shared_count对象,__shared_count对象包含一个指向管理对象(manager object)的基类指针,管理对象(manager object)由具有原子属性的use_count和weak_count、指向被管理对象(managed object)T的指针、以及用来销毁被管理对象的deleter组成,以下均将用new创建后托管给shared_ptr等智能指针管理的对象叫做被管理对象(managed object);shared_ptr等智能指针内部创建的用来维护被管理对象生命周期的实例叫做管理对象(manager object):
图2
weak_ptr内部组成与shared_ptr类似,内部同样含有一个指向被管理对象T的指针以及一个__weak_count对象:
图3
从图2和图3对比可以看出,shared_ptr与weak_ptr的差异主要是由__shared_ptr与__weak_ptr体现出来的,而__shared_ptr与__weak_ptr的差异则主要是由__shared_count与__weak_count体现出来。
通过shared_ptr的构造函数,可以发现,在创建一个shared_ptr的时候需要一个new 操作符返回被管理对象的地址来初始化shared_ptr, shared_ptr在内部会构建一个_shared_count对象,由_shared_count对象的构造函数可知,创建shared_ptr的时候也动态的创建了一个管理对象_Sp_counted_base_impl:
template<typename _Tp1> explicit __shared_ptr(_Tp1* __p)
: _M_ptr(__p), _M_refcount(__p) {
__glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>)
typedef int _IsComplete[sizeof(_Tp1)];
__enable_shared_from_this_helper(_M_refcount, __p, __p);
}
template<typename _Ptr>
__shared_count(_Ptr __p) : _M_pi(0)
{
__try
{
typedef typename std::tr1::remove_pointer<_Ptr>::type _Tp;
_M_pi = new _Sp_counted_base_impl<_Ptr, _Sp_deleter<_Tp>, _Lp>(__p, _Sp_deleter<_Tp>());
}
__catch(...)
{
delete __p;
__throw_exception_again;
}
}
shared_ptr内部包含一个指向被管理对象的指针_M_ptr, _Sp_counted_base_impl内部也含有一个指向被管理对象的指针_M_ptr, 它们是不是重复多余了呢?
实际上不多余,它们有各自的功能。这首先要从shared_ptr的拷贝构造或者赋值构造说起,当一个shared_ptr对象sp2是由sp1拷贝构造或者赋值构造得来的时候,实际上构造完成后sp1内部的__shared_count对象包含的指向管理对象的指针与sp2内部的__shared_count对象包含的指向管理对象的指针是相等的,也就是说当多个shared_ptr对象来管理同一个对象时,它们共同使用同一个动态分配的管理对象。这可以从下面的__share_ptr的构造函数和__shared_count的构造函数清楚的看出。
template<typename _Tp1>
__shared_ptr(const __shared_ptr<_Tp1, _Lp>& __r)
: _M_ptr(__r._M_ptr), _M_refcount(__r._M_refcount) // never throws
{__glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>)}
__shared_count&
operator=(const __shared_count& __r) // nothrow
{
_Sp_counted_base<_Lp>* __tmp = __r._M_pi;
if (__tmp != _M_pi)
{
if (__tmp != 0)
__tmp->_M_add_ref_copy();
if (_M_pi != 0)
_M_pi->_M_release();
_M_pi = __tmp;
}
}
上面说说当多个shared_ptr对象来管理同一个对象时,它们共同使用同一个动态分配的管理对象,为什么上面给出的_shared_count的构造函数中出现了__tmp != _M_pi的情形呢?这在sp2未初始化时(_M_pi为0,_r._M_pi非0)便是这样的情形。
更一般的,也可以考虑这样的情形:shared_ptr实例sp1开始指向类A的实例对象a1, 另外一个shared_ptr实例sp2指向类A的实例对象a2(a1 != a2),当把sp2赋值给sp1时便会出现上面的情形。假设初始时有且仅有一个sp1指向a1, 有且仅有一个sp2指向a2; 则赋值结束时sp1与sp2均指向a2, 没有指针指向a1, sp1指向的a1以及其对应的管理对象均应该被析构。这在上面的代码中我们可以很清楚的看到:因为__tmp != _M_pi, __tmp->_M_add_ref_copy()将会增加a2的use_count的引用计数;由于a1内部的_M_pi != 0, 将会调用其_M_release()函数:
//************_Sp_counted_base*****************//
void
_M_add_ref_copy()
{ __gnu_cxx::__atomic_add_dispatch(&_M_use_count, 1); }
//************_Sp_counted_base*****************//
void
_M_release() // nothrow
{
// Be race-detector-friendly. For more info see bits/c++config.
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_use_count);
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1)
{
_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_use_count);
_M_dispose();
// There must be a memory barrier between dispose() and destroy()
// to ensure that the effects of dispose() are observed in the
// thread that runs destroy().
// See http://gcc.gnu.org/ml/libstdc++/2005-11/msg00136.html
if (_Mutex_base<_Lp>::_S_need_barriers)
{
__atomic_thread_fence (__ATOMIC_ACQ_REL);
}
// Be race-detector-friendly. For more info see bits/c++config.
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count);
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count, -1) == 1)
{
_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count);
_M_destroy();
}
}
}
//************_Sp_counted_base*****************//
// Called when _M_use_count drops to zero, to release the resources
// managed by *this.
virtual void
_M_dispose() = 0; // nothrow
// Called when _M_weak_count drops to zero.
virtual void
_M_destroy() // nothrow
{ delete this; }
//************_Sp_counted_base_impl*************//
virtual void
_M_dispose() // nothrow
{ _M_del(_M_ptr); }
_M_release()函数首先对a1的use_count减去1,并对比减操作之前的值,如果减之前是1,说明减后是0,a1没有任何shared_ptr指针指向它了,应该将a1对象销毁,于是调用_M_dispose()函数销毁a1; 同时对a1的weak_count减去1,也对比减操作之前的值,如果减之前是1,说明减后是0,a1没有weak_ptr指向它了,应该将管理对象销毁,于是调用_M_destroy()销毁了管理对象。这就可以解答为什么图2所示中shared_ptr内部含有两个指向被管理对象的指针了:__shared_ptr直接包含的裸指针是为了实现一般指针的->,*等操作,通过__shared_count间接包含的指针是为了管理对象的生命周期,回收相关资源。
换句话说,__shared_count内部的use_count主要用来标记被管理对象的生命周期,weak_count主要用来标记管理对象的生命周期。
当一个shared_ptr超出作用域被销毁时,它会调用其_share_count的_M_release()对use_count和weak_count进行自减并判断是否需要释放管理对象和被管理对象,这是RAII原理的核心体现:
~__shared_count() // nothrow
{
if (_M_pi != 0)
_M_pi->_M_release();
}
对于weak_ptr, 其对应的__weak_count的拷贝构造函数如下:
//************_Sp_counted_base*****************//
void
_M_weak_add_ref() // nothrow
{ __gnu_cxx::__atomic_add_dispatch(&_M_weak_count, 1); }
//************_Sp_counted_base*****************//
void
_M_weak_release() // nothrow
{
// Be race-detector-friendly. For more info see bits/c++config.
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count);
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count, -1) == 1)
{
_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count);
if (_Mutex_base<_Lp>::_S_need_barriers)
{
// See _M_release(),
// destroy() must observe results of dispose()
__atomic_thread_fence (__ATOMIC_ACQ_REL);
}
_M_destroy();
}
}
__weak_count<_Lp>&
operator=(const __shared_count<_Lp>& __r) // nothrow
{
_Sp_counted_base<_Lp>* __tmp = __r._M_pi;
if (__tmp != 0)
__tmp->_M_weak_add_ref();
if (_M_pi != 0)
_M_pi->_M_weak_release();
_M_pi = __tmp;
return *this;
}
__weak_count<_Lp>&
operator=(const __weak_count<_Lp>& __r) // nothrow
{
_Sp_counted_base<_Lp>* __tmp = __r._M_pi;
if (__tmp != 0)
__tmp->_M_weak_add_ref();
if (_M_pi != 0)
_M_pi->_M_weak_release();
_M_pi = __tmp;
return *this;
}
__weak_count<_Lp>&
operator=(const __shared_count<_Lp>& __r) // nothrow
{
_Sp_counted_base<_Lp>* __tmp = __r._M_pi;
if (__tmp != 0)
__tmp->_M_weak_add_ref();
if (_M_pi != 0)
_M_pi->_M_weak_release();
_M_pi = __tmp;
return *this;
}
~__weak_count() // nothrow
{
if (_M_pi != 0)
_M_pi->_M_weak_release();
}
从上面可以看出:
__weak_count相关的赋值拷贝以及析构函数均只会影响到weak_count的值,对use_count没有影响;当weak_count为0时,释放管理对象。也就是说__weak_ptr不影响被管理对象的生命周期。同时由于__weak_ptr没有像__shared_ptr那样实现*,->等常见指针相关操作符,__weak_ptr不能直接操作被管理对象;
__weak_count自身间的赋值以及__shared_count对__weak_count的赋值时,它们都具有同样的指向管理对象的指针;也就是说当多个__weak_ptr和__shared_ptr指向同一个被管理对象时,它们共享同一个管理对象,这就保证了可以通过__weak_ptr可以判断__shared_ptr指向的被管理对象是否存在以及获取到被管理对象的指针。
__shared_ptr与__weak_ptr在管理同一对象时,它们间的关系如下图4所示:
图4
由于weak_ptr不能直接操作被管理对象但其仍然持有指向被管理对象的指针(用来初始化内部的__weak_count对象),weak_ptr与被管理对象用虚线联接。
_weak_ptr有几个重要的成员函数:通过expired()方法来判断对象是否过期(已经被释放);通过use_count()方法返回目前有多少个__shared_ptr对象指向被管理对象;通过lock()方法返回一个指向被管理对象的__shared_ptr指针,调用者可以通过这个__shared_ptr指针来操纵被管理对象而不用担心资源泄漏;
/*************_weak_ptr*************************/
long
use_count() const // never throws
{ return _M_refcount._M_get_use_count(); }
bool
expired() const // never throws
{ return _M_refcount._M_get_use_count() == 0; }
__shared_ptr<_Tp, _Lp>
lock() const // never throws
{
#ifdef __GTHREADS
// Optimization: avoid throw overhead.
if (expired())
return __shared_ptr<element_type, _Lp>();
__try
{
return __shared_ptr<element_type, _Lp>(*this);
}
__catch(const bad_weak_ptr&)
{
// Q: How can we get here?
// A: Another thread may have invalidated r after the
// use_count test above.
return __shared_ptr<element_type, _Lp>();
}
#else
// Optimization: avoid try/catch overhead when single threaded.
return expired() ? __shared_ptr<element_type, _Lp>()
: __shared_ptr<element_type, _Lp>(*this);
#endif
} // XXX MT
当然shared_ptr也不是万能的,使用的时候也要注意到它给程序员挖的一个大坑:shared_ptr能够管理对象的生命周期,负责对象资源释放,其前提条件是所有shared_ptr共用同一个管理对象。如果多个shared_ptr使用多个管理对象来管理同一个被管理对象,这些管理对象在use_count为0时均会释放被管理对象,将会造成多个管理对象多次释放被管理对象,造成twice delete的堆错误。下面的例子在单独使用裸指针的时候没有问题,采用shared_ptr将会出现twice delete的问题:
class Thing {
public:
void foo();
void defrangulate();
};
void transmogrify(Thing *);
int main()
{
Thing * t1 = new Thing;
t1->foo();
...
delete t1; // done with the object
}
...
void Thing::foo()
{
// we need to transmogrify this object
transmogrify(this);
}
void transmogrify(Thing * ptr)
{
ptr->defrangulate();
/* etc. */
}
//***** Use shared_ptr***************************//
class Thing {
public:
void foo();
void defrangulate();
};
void transmogrify(shared_ptr<Thing>);
int main()
{
shared_ptr<Thing> t1(new Thing); // create manager object A for the Thing
t1->foo();
...
// Thing is supposed to get deleted when t1 goes out of scope
}
void Thing::foo()
{
// we need to transmogrify this object
shared_ptr<Thing> sp_for_this(this); // create manager object B for the Thing
transmogrify(sp_for_this);
// Thing is supposed to get deleted when sp_for_this and other shared_ptr goes out of scope
}
void transmogrify(shared_ptr<Thing> ptr)
{
ptr->defrangulate();
/* etc. */
}
上面注释处分别创建了两个shared_ptr指针t1,sp_for_this, 它们各自有自己的管理对象,但被管理的堆内存却是一样的,这就导致在t1和sp_for_this析构时,它们各自的管理对象均会析构被管理对象,造成twice delete。
怎样解决上面这一广泛存在问题:当一个对象M创建后,如果一个函数f(另一个类的成员函数或是其它自由函数)的形参为M类型的智能指针,如何在对象M内部将对象M的指针作为实参传递给该函数f ? C++引入了enable_shared_from_this利用weak_ptr的特性解决了这一问题。其基本思想是通过M继承模板类enable_shared_from_this,这样对象M内部将会有一个__weak_ptr指针_M_weak_this,在第一次创建指向M的shared_ptr Pt时,通过模板特化,将会初始化_M_weak_this;这样M内部也会产生一个指向自身的weak_ptr,并且该weak_ptr内部的管理对象与Pt的管理对象是相同的(这可以从weak_ptr内部的_M_assign函数看出)。
// Friend of enable_shared_from_this.
template<typename _Tp1, typename _Tp2>
void __enable_shared_from_this_helper(const __shared_count<>&, const enable_shared_from_this<_Tp1>*, const _Tp2*);
template<typename _Tp1>
explicit __shared_ptr(_Tp1* __p)
: _M_ptr(__p), _M_refcount(__p)
{
__glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>) typedef int _IsComplete[sizeof(_Tp1)];
__enable_shared_from_this_helper(_M_refcount, __p, __p);
}
template<typename _Tp>
class enable_shared_from_this
{
protected:
enable_shared_from_this() { }
enable_shared_from_this(const enable_shared_from_this&) { }
enable_shared_from_this&
operator=(const enable_shared_from_this&)
{ return *this; }
~enable_shared_from_this() { }
public:
shared_ptr<_Tp>
shared_from_this()
{ return shared_ptr<_Tp>(this->_M_weak_this); }
shared_ptr<const _Tp>
shared_from_this() const
{ return shared_ptr<const _Tp>(this->_M_weak_this); }
private:
template<typename _Tp1>
void
_M_weak_assign(_Tp1* __p, const __shared_count<>& __n) const
{ _M_weak_this._M_assign(__p, __n); }
template<typename _Tp1>
friend void
__enable_shared_from_this_helper(const __shared_count<>& __pn, const enable_shared_from_this* __pe, const _Tp1* __px)
{
if (__pe != 0)
__pe->_M_weak_assign(const_cast<_Tp1*>(__px), __pn);
}
mutable weak_ptr<_Tp> _M_weak_this;
};
_M_assign(_Tp* __ptr, const __shared_count<_Lp>& __refcount)
{
_M_ptr = __ptr;
_M_refcount = __refcount;
}
这样,在M内部,当需要传递指向M的智能指针时,可以通过继承而来的shared_from_this方法获取到指向M的智能指针而不会发生内存泄漏。上面示例中改写后的正确代码为:
class Thing : public enable_shared_from_this<Thing> {
public:
void foo();
void defrangulate();
};
int main()
{
// The following starts a manager object for the Thing and also
// initializes the weak_ptr member that is now part of the Thing and share same manager object.
shared_ptr<Thing> t1(new Thing);
t1->foo();
...
}
void Thing::foo()
{
// get a shared_ptr from the weak_ptr in this object
shared_ptr<Thing> sp_this = shared_from_this();
transmogrify(sp_this);
}
void transmogrify(shared_ptr<Thing> ptr)
{
ptr->defrangulate();
/* etc. */
}
解决了所有的坑,shared_ptr是不是就十全十美了呢?当然不是,shared_ptr也存在不足:在采用shared_ptr<M> p(new M);形式创建p来管理M时,我们实际发现这中间有两次的动态内存分配:一次为创建被管理对象M,一次为创建管理对象;而内存分配通常是比较昂贵的操作。
如果频繁的需要创建指向多个不同对象的智能指针,可以采用shared_ptr<M> p(make_shared<M>);的方式,采用这种方式系统将会分配一大块内存同时存放管理对象和被管理对象,这就避免了上面所说的二次内存分配的问题,同时程序中也不会出现new操作符,符合"no naked new!"的编程倡导。当然这也有缺点,如果所有指向该对象的智能指针都销毁了,尽管对象的析构函数会被调用,析构被管理对象,但是如果还有weak_ptr指向该块对象所在的内存,存放管理对象的部分内存仍将不会被释放,因而导致在所有其他weak_ptr销毁前整块内存(尽管被管理对象已经析构了)将不会进入系统的内存池循环使用。
————————————————
版权声明:本文为CSDN博主「ithiker」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ithiker/article/details/51532484