vs2010的std::tr1::shared_ptr和boost的shared_ptr比起来简单易懂,代码短小精悍。我没有细读过boost::shared_ptr的代码,粗略看过,细节上的区别还是很大的。
大致结构
shared_ptr是一个模板类,派生自_Ptr_base。其中_Ptr_base内记录的_Ty*是用户创建shared_ptr时传入的要管理的指针;而_Ptr_base内还有一个成员变量,是一个_Ref_count_base的指针。它是实现引用计数功能的主力。
关键的_Ref_count_base
_Ref_count_base 是在shared_ptr创建的时候new出来的。
_Ref_count_base有3个子类,分别对应shared_ptr构造时的3种形式。在shared_ptr的实现中,会根据调用的shared_ptr构造函数的不同,使用不同类型的_Ref_count_base创建。
_Ref_count_base中保存了两个引用计数:
l _Uses:用于shared_ptr,表示该_Ref_count_base被几个shared_ptr引用了。当_Uses等于0时,会释放_Ref_count_base中保存的对象(也就是用户传入的指针)。
l _Weaks:用于weak_ptr,表示该_Ref_count_base被几个weak_ptr引用了。当_ Weaks等于0时,会释放_Ref_count_base自身。
有了这两个引用计数,就能实现只能指针管理资源基本功能以及野指针检查的功能。
构造函数中奇怪的参数
在shared_ptr和weak_ptr的构造函数中均有几个显眼的重载项,例如:
template<class _Ty2>
shared_ptr(const shared_ptr<_Ty2>& _Other,
typename enable_if<is_convertible<_Ty2 *, _Ty *>::value,
void *>::type * = 0)
{ // construct shared_ptr object that owns same resource as _Other
this->_Reset(_Other);
}
这里enable_if<is_convertible<_Ty2 *, _Ty *>::value, void *>::type * = 0 这个参数究竟表达什么意思呢?
这其实是一个模版元编程的一个惯用手法,即type_traits。利用编译期推导的特性,让编译器选择何时重载的函数。
这里的意思就是如果出现这样的代码:
class A{};
class B:public A{};
shared_ptr<A> pA(new A);
shared_ptr<B> pB(pA);
当执行shared_ptr<B> pB(pA);时会走上面的函数。换句话说,如果B和A没有派生关系,那么编译器就找不到对应的重载函数,就会在编译期提示错误。
OK,深入到细节仔细看一下这个type_traits的实现。其中,is_convertible<_Ty2 *, _Ty *>会被展开为以下代码:
template<bool>
struct _Cat_base;
template<>
struct _Cat_base<false>
: false_type
{ // base class for type predicates
};
template<>
struct _Cat_base<true>
: true_type
{ // base class for type predicates
};
template<class _From, class _To>
struct is_convertible :
_Cat_base<is_void<_From>::value &&
is_void<_To>::value ||
__is_convertible_to(_From, _To)>
{ // determine whether _From is convertible to _To
};
其中__is_convertible_to是一个编译器内部支持的函数,能够获取到两个类之间是否能够转换。详见http://technet.microsoft.com/zh-cn/library/ms177194(v=vs.80)。当然这里还有两种特殊情况,就是如果_From和_To如果都是void类型那么也是可以转换的。
_Cat_base在这里只起到一个把多个布尔值组合起来的功能。_Cat_base有1个泛型实现有2个特化版本。
再看enable_if的实现,手法和_Cat_base如出一辙。实现偏特化的类让编译器选择。
// TEMPLATE CLASS enable_if
template<bool _Test,
class _Type = void>
struct enable_if
{ // type is undefined for assumed !_Test
};
template<class _Type>
struct enable_if<true, _Type>
{ // type is _Type for _Test
typedef _Type type;
};
也就是说只有当is_convertible求值为_Cat_base<true>才可能让编译器推导出enable_if<is_convertible<_Ty2 *, _Ty *>::value,void *>::type。
shared_ptr创建过程
选取一个带有删除器的构造函数作为例子:
template<class _Ux,
class _Dx>
shared_ptr(_Ux *_Px, _Dx _Dt)
{ // construct with _Px, deleter
_Resetp(_Px, _Dt);
}
在该构造函数中,转发调用下面的函数:
template<class _Ux,
class _Dx>
void _Resetp(_Ux *_Px, _Dx _Dt)
{ // release, take ownership of _Px, deleter _Dt
_TRY_BEGIN // allocate control block and reset
_Resetp0(_Px, new _Ref_count_del<_Ux, _Dx>(_Px, _Dt));
_CATCH_ALL // allocation failed, delete resource
_Dt(_Px);
_RERAISE;
_CATCH_END
}
可以看出,在该函数中new了一个_Ref_count_del的类。_Resetp0这个函数是基类_Ptr_base的一个成员函数:
template<class _Ux>
void _Resetp0(_Ux *_Px, _Ref_count_base *_Rx)
{ // release resource and take ownership of _Px
this->_Reset0(_Px, _Rx);
_Enable_shared(_Px, _Rx);(空实现)
}
在这个函数中调用了_Ref_count_base的_Reset0函数,同时还有一个_Enable_shared函数,不过_Enable_shared函数只有当_Px派生自enable_shared_from_this的类有用,对于一般类而言是空实现。
接着看一下构造过程的终点:_Ref_count_base:: Reset0函数:
void _Reset0(_Ty *_Other_ptr, _Ref_count_base *_Other_rep)
{ // release resource and take new resource
if (_Rep != 0)
_Rep->_Decref();
_Rep = _Other_rep;
_Ptr = _Other_ptr;
}
很简单的赋值操作,引用计数实现的惯用手法。如果引用计数已经有值的话需要先减去引用计数。下面看减去引用计数的操作:
void _Decref()
{ // decrement use count
if (_MT_DECR(_Mtx, _Uses) == 0)
{ // destroy managed resource, decrement weak reference count
_Destroy(); //纯虚函数
_Decwref();
}
}
这里如果引用计数减到0那么就调用_Destroy函数和_Decwref函数。这里_Destroy函数是_Ref_count_base的一个纯虚函数。该纯虚函数的意图是:如何删除引用计数管理的对象。很容易想象,有删除器的实现自然是调用删除器来完成这个动作,没有删除器的话就调用delete。
再看_Decwref函数。这个函数和weak_ptr有关系,后面讲。实现可以先看下:
void _Decwref()
{ // decrement weak reference count
if (_MT_DECR(_Mtx, _Weaks) == 0)
_Delete_this();
}
_Delete_this函数也是_Ref_count_base的一个纯虚函数。该纯虚函数的意图是:释放自身的方式(即_Ref_count_base)。
shared_ptr的析构过程
~shared_ptr()
{ // release resource
this->_Decref();
}
调用_Ptr_base:: _Decref()函数:
void _Decref()
{ // decrement reference count
if (_Rep != 0)
_Rep->_Decref();
}
调用_Ref_count_base:: _Decref()函数:
void _Decref()
{ // decrement use count
if (_MT_DECR(_Mtx, _Uses) == 0)
{ // destroy managed resource, decrement weak reference count
_Destroy(); //纯虚函数
_Decwref();
}
}
接下来的过程就和前文中提到的shared_ptr构造时的过程一模一样了。
从shared_ptr的析构过程可以看出来。由于weak_ptr引用计数的原因,有可能当shared_ptr析构了以后,对应的管理的对象也被释放了,但是_Ref_count_base却还在内存中留存。一定要等到所有的weak_ptr都释放以后才会彻底释放。
shared_ptr拷贝构造过程
拷贝构造的过程其实和构造函数流程基本一致:
shared_ptr(const _Myt& _Other)
{ // construct shared_ptr object that owns same resource as _Other
this->_Reset(_Other);
}
到这里就会发现拷贝构造的过程和构造过程已经一样了:
template<class _Ty2>
void _Reset(const _Ptr_base<_Ty2>& _Other)
{ // release resource and take ownership of _Other._Ptr
_Reset(_Other._Ptr, _Other._Rep, false);
}
shared_ptr右值拷贝构造过程
shared_ptr(_Myt&& _Right)
: _Mybase(_STD forward<_Myt>(_Right))
{ // construct shared_ptr object that takes resource from _Right
}
这里直接调用的是基类的rvalue拷贝构造:
template<class _Ty2>
_Ptr_base(_Ptr_base<_Ty2>&& _Right)
: _Ptr(_Right._Ptr), _Rep(_Right._Rep)
{ // construct _Ptr_base object that takes resource from _Right
_Right._Ptr = 0;
_Right._Rep = 0;
}
由于_Ptr_base里面也没什么内存块,所以这里的右值拷贝基本和普通拷贝没什么区别。
shared_ptr赋值的过程
赋值的过程,调用拷贝构造然后swap:
template<class _Ty2>
_Myt& operator=(const shared_ptr<_Ty2>& _Right)
{ // assign shared ownership of resource owned by _Right
shared_ptr(_Right).swap(*this);
return (*this);
}
首先是把参数临时拷贝了一份,然后用 STL中随处可见的swap大法。保证异常安全。
void swap(_Myt& _Other)
{ // swap pointers
this->_Swap(_Other);
}
这里调用的是基类_Ptr_base的函数:
void _Swap(_Ptr_base& _Right)
{ // swap pointers
_STD swap(_Rep, _Right._Rep);
_STD swap(_Ptr, _Right._Ptr);
}
_STD是一个宏,展开就是std::。这里调用的是std::swap函数,该函数在utility.h:
// TEMPLATE FUNCTION swap (from <algorithm>)
template<class _Ty> inline
void swap(_Ty& _Left, _Ty& _Right)
{ // exchange values stored at _Left and _Right
_Ty _Tmp = _Move(_Left);
_Left = _Move(_Right);
_Right = _Move(_Tmp);
}
再看_Move函数:
// TEMPLATE FUNCTION _Move
template<class _Ty> inline
typename tr1::_Remove_reference<_Ty>::_Type&&
_Move(_Ty&& _Arg)
{ // forward _Arg as movable
return ((typename tr1::_Remove_reference<_Ty>::_Type&&)_Arg);
}
到这里也就仅仅是把右值引用拿过来,通过分析得知,基类_Ptr_base的swap中模板推导参数就应该是_Ref_count_base。所以swap函数中实际会调用_Ref_count_base的右值=,不过_Ref_count_base并没有定义该操作符,所以最终也就是普通的指针赋值了。
weak_ptr大致结构
weak_ptr同样是派生自_Ptr_base,weak_ptr本身没有任何成员变量。但是在_Ref_count_base中保存了一个专门用于weak_ptr的引用计数。
weak_ptr构造的过程
构造过程和shared_ptr也差不多:
template<class _Ty2>
weak_ptr(const shared_ptr<_Ty2>& _Other,
typename enable_if<is_convertible<_Ty2 *, _Ty *>::value,
void *>::type * = 0)
{ // construct weak_ptr object for resource owned by _Other
this->_Resetw(_Other);
}
这里稍微一个不同的地方是,这里用了一个type_traits:enable_if。但是实际上这个参数并没有作用,也没有重载的作用。
接下来就是调用基类的_Resetw函数(这里的w值得就是weak_ptr的意思):
template<class _Ty2>
void _Resetw(const _Ptr_base<_Ty2>& _Other)
{ // release weak reference to resource and take _Other._Ptr
_Resetw(_Other._Ptr, _Other._Rep);
}
template<class _Ty2>
void _Resetw(_Ty2 *_Other_ptr, _Ref_count_base *_Other_rep)
{ // point to _Other_ptr through _Other_rep
if (_Other_rep)
_Other_rep->_Incwref();
if (_Rep != 0)
_Rep->_Decwref();
_Rep = _Other_rep;
_Ptr = _Other_ptr;
}
这里并没有判断传入的_Other_ptr是不是等于自身的_Ptr。而是字节先增加引用计数然后再把原本的类成员变量记录的weak_ptr的引用计数减去。
可以很明显的看到。每个shared_ptr构造的时候都会new一个_Ref_count_base出来,但是weak_ptr就不会了。weak_ptr只能通过shared_ptr构造,直接把shared_ptr的_Ref_count_base拿出来用了。
weak_ptr的析构过程
~weak_ptr()
{ // release resource
this->_Decwref();
}
void _Decwref()
{ // decrement weak reference count
if (_Rep != 0)
_Rep->_Decwref();
}
void _Decwref()
{ // decrement weak reference count
if (_MT_DECR(_Mtx, _Weaks) == 0)
_Delete_this();
}
weak_ptr析构的时候,会将自身引用的_Ref_count_base的weak引用计数-1。在_Ref_count_base的实现中,如果weak引用计数等于0,那么就删除自身(_Ref_count_base)。
weak_ptr是怎么实现野指针检测的
这个问题实际上很简单:weak_ptr使用expired函数判断shared_ptr是否已经失效了:
bool expired() const
{ // return true if resource no longer exists
return (this->_Expired());
}
调用的是基类的_Expired:
bool _Expired() const
{ // test if expired
return (!_Rep || _Rep->_Expired());
}
调用_Ref_count_base的_Expired():
bool _Expired() const
{ // return true if _Uses == 0
return (_Uses == 0);
}
可以看到,实际上检查shared_ptr是不是已经被释放掉了是通过检查_Ref_count_base来实现的。也就是说_Ref_count_base中的两个引用计数都等于0时,_Ref_count_base自己才会被释放掉。
enable_shared_from_this是如何工作的
首先要说明一个 enable_shared_from_this的坑:
1、 一个类C派生自enable_shared_from_this,那么不能在类C的构造函数中调用shared_from_this()。
2、 一个类C派生自enable_shared_from_this,如果需要使用shared_from_this这个功能,那么创建C时只能使用shared_ptr,使用普通的new或者在栈上创建都是不行的。
下面看下enable_shared_from_this代码,其实不过是一个weak_ptr:
template<class _Ty> class enable_shared_from_this
{ // provide member functions that create shared_ptr to this
public:
typedef _Ty _EStype;
shared_ptr<_Ty> shared_from_this()
{ // return shared_ptr
return (shared_ptr<_Ty>(_Wptr));
}
private:
mutable weak_ptr<_Ty> _Wptr;
};
当你写下shared_ptr<CC> c(new CC);时,会发生的事情前文都已经交代,唯独有一个函数:构造shared_ptr的时候在_Ptr_base中有个函数:
template<class _Ux>
void _Resetp0(_Ux *_Px, _Ref_count_base *_Rx)
{ // release resource and take ownership of _Px
this->_Reset0(_Px, _Rx);
_Enable_shared(_Px, _Rx);
}
这里_Enable_shared前面说是空实现。但是针对派生自enable_shared_from_this的类就不是空实现了:
template<class _Ty>
inline void _Enable_shared(_Ty *_Ptr, _Ref_count_base *_Refptr,
typename _Ty::_EStype * = 0)
{ // reset internal weak pointer
if (_Ptr)
_Do_enable(_Ptr,(enable_shared_from_this<typename _Ty::_EStype>*)_Ptr, _Refptr);
}
template<class _Ty1,class _Ty2>
inline void _Do_enable(_Ty1 *_Ptr,enable_shared_from_this<_Ty2> *_Es,
_Ref_count_base *_Refptr)
{ // reset internal weak pointer
_Es->_Wptr._Resetw(_Ptr, _Refptr);
}
可以看到,在shared_ptr构造的时候,针对派生自enable_shared_from_this的类,会把类中的_Wptr这个成员变量赋值。这个操作至关重要,是enable_shared_from_this能够发挥作用的关键。
那为什么不能在构造函数中使用shared_from_this呢?这个原因很简单,因为_Wptr这个成员变量赋值是在shared_ptr的构造函数中,而shared_ptr的构造函数是在类CC构造函数之后执行的。
线程安全性
这里有一篇好文,是介绍boost::shared_ptr的线程安全性的:《为什么多线程读写 shared_ptr 要加锁?》http://blog.csdn.net/solstice/article/details/8547547
从数据结构上来看,shared_ptr以及weak_ptr的数据结构设计的非常简洁。只有_Ref_count_base这个成员是new出来的,它是可能被多个shared_ptr/weak_ptr实例共享的。
那么如果有两个处于不同线程的shared_ptr同时读写某一个_Ref_count_base时,如何保证线程安全呢?shared_ptr使用stl中普遍使用swap方法来将这个问题尽量减到最低。所以在shared_ptr的实现中,你会发现很多函数都是这样实现的:
template<class _Ty2>
_Myt& operator=(const shared_ptr<_Ty2>& _Right)
{ // assign shared ownership of resource owned by _Right
shared_ptr(_Right).swap(*this);
return (*this);
}
再看_Ref_count_base,里面有两个引用计数的成员。vs2010的实现中,使用了interlock的函数对这两个引用计数成员进行操作。
#define _MT_INCR(mtx, x) _InterlockedIncrement(&x)
#define _MT_DECR(mtx, x) _InterlockedDecrement(&x)
#define _MT_CMPX(x, y, z) _InterlockedCompareExchange(&x, y, z)
例如:
void _Incref()
{ // increment use count
_MT_INCR(_Mtx, _Uses);
}
void _Incwref()
{ // increment weak reference count
_MT_INCR(_Mtx, _Weaks);
}
void _Decref()
{ // decrement use count
if (_MT_DECR(_Mtx, _Uses) == 0)
{ // destroy managed resource, decrement weak reference count
_Destroy();
_Decwref();
}
}
void _Decwref()
{ // decrement weak reference count
if (_MT_DECR(_Mtx, _Weaks) == 0)
_Delete_this();
}
这样的做法使得_Ref_count_base本身是线程安全的,但是并不能完全保证能够做到多线程同时读写同一个shared_ptr对象。所以我认为这里使用interlock的函数没什么必要。
异常安全性
首先回顾下C++异常安全性的三个级别:
l 异常安全的基本保证:如果有异常抛出,资源不会泄漏。对象保持可摧毁、可使用但是不一定可预测的状态。它使用的场合是:失败的操作已经对对象状态做了改变,但是调用者代码依然能够应付。
l 异常安全的强烈保证:如果有异常抛出,程序状态保持不变。这一级别总是蕴含这”提交或者回退”的语义。例如:如果操作失败指向容器内部的引用或者迭代器不会失效
l 不抛出异常保证:在任何情况下,函数都不会产生异常。这一点对于编写底层库函数来说尤其重要。
在std::tr1::shared_ptr的实现中,绝大多数函数中都恪守不抛出异常的原则。只有3个函数直接抛出异常的地方,不过也要看是否定义了HAS_EXCEPTIONS宏。这3个函数其实是同一个函数的3种重载,对应3种shared_ptr的构造方式:
template<class _Ux>
void _Resetp(_Ux *_Px)
{ // release, take ownership of _Px
_TRY_BEGIN // allocate control block and reset
_Resetp0(_Px, new _Ref_count<_Ux>(_Px));
_CATCH_ALL // allocation failed, delete resource
delete _Px;
_RERAISE;
_CATCH_END
}
template<class _Ux,
class _Dx>
void _Resetp(_Ux *_Px, _Dx _Dt)
{ // release, take ownership of _Px, deleter _Dt
_TRY_BEGIN // allocate control block and reset
_Resetp0(_Px, new _Ref_count_del<_Ux, _Dx>(_Px, _Dt));
_CATCH_ALL // allocation failed, delete resource
_Dt(_Px);
_RERAISE;
_CATCH_END
}
//#if _HAS_CPP0X
template<class _Ux,
class _Dx,
class _Alloc>
void _Resetp(_Ux *_Px, _Dx _Dt, _Alloc _Ax)
{ // release, take ownership of _Px, deleter _Dt, allocator _Ax
typedef _Ref_count_del_alloc<_Ux, _Dx, _Alloc> _Refd;
typename _Alloc::template rebind<_Refd>::other _Al = _Ax;
_TRY_BEGIN // allocate control block and reset
_Refd *_Ptr = _Al.allocate(1);
new (_Ptr) _Refd(_Px, _Dt, _Al);
_Resetp0(_Px, _Ptr);
_CATCH_ALL // allocation failed, delete resource
_Dt(_Px);
_RERAISE;
_CATCH_END
}
意图其实也很简单:如果调用_ Resetp0的过程中有异常抛出,把new出来的对象删除掉,然后把异常传递出去。根据_Resetp0函数代码分析,_Resetp0执行过程中最有可能抛出异常的情况有:
l 内存不足,bad alloc异常被抛出
l 在调用_Ref_count_base::_Decref的过程中会调用_Destroy(),这是个纯虚函数,可能被实现为抛出异常。
l 用户实现的析构函数可能会抛出异常。
但是,仔细看这几个try包住的地方,全部都是new一个全新的_Ref_count_base,也就是说可能抛出异常的情况2和情况3不可能出现。只可能出现内存不足抛出异常的情况。所以我认为这里的异常处理画蛇添足。
————————以上摘自:std::tr1::shared_ptr源码赏析