Boost::scoped_ptr
Boost::scoped_ptr是指向自动(栈)对象的、不可复制(noncopyable)的智能指针。Boost::scoped_ptr模板类存储的是指向动态分配的对象(通过new 分配)的指针。被指向的对象保证会被删除。删除的时机在Boost::scoped_ptr析构时,或者通过显式地调用Boost::scoped_ptr::reset方法。
Boost::scoped_ptr模板没有“共享所有权”或是“所有权转让”语义。因为它是不可拷贝的,对于不可拷贝的指针来说,它比Boost::shared_ptr 或 std::auto_ptr 更加安全。如果不小心对Boost::scoped_ptr进行了拷贝操作,编译将会出错。
namespace boost { template<class T> class scoped_ptr : noncopyable { public: typedef T element_type; explicit scoped_ptr(T * p = 0); // never throws ~scoped_ptr(); // never throws void reset(T * p = 0); // never throws T & operator*() const; // never throws T * operator->() const; // never throws T * get() const; // never throws operator unspecified-bool-type() const; // never throws void swap(scoped_ptr & b); // never throws }; template<class T> void swap(scoped_ptr<T> & a, scoped_ptr<T> & b); // never throws }
Boost::noncopyable将拷贝构造函数和赋值函数私有化,任何继承自他的子类,将同样不能进行拷贝操作。Swap用来交换两个智能指针的内容。unspecified-bool-type被定义为指向成员函数的指针的类型。C++标准规定了对非静态成员取地址,得到的值为1。这样unspecified_bool_type返回的非0即1。编译器在碰到对Boost::scoped_ptr智能指针的Bool运算时,会尝试进行隐式的类型转换。这种情况下,unspecified-bool-type是唯一的选择。对Boost::scoped_ptr智能指针进行if,!,!=,==操作,都是unspecified-bool-type帮助完成的。
typedef T * this_type::*unspecified_bool_type; operator unspecified_bool_type() const { return px == 0? 0: &this_type::px; }
unspecified-bool-type利用到的两个C++标准:
为什么不直接定义一个operator bool() const呢?
由于Boost::scoped_ptr没有拷贝语义,不能用于STL的容器。为什么呢?C++标准规定,STL元素必须具备拷贝构造和可赋值的特征。Boost::scoped_ptr显然不满足。
由于Boost::scoped_ptr的构造函数被定义为explicit,不能通过赋值来初始化Boost::scoped_ptr。
std::auto_ptr
std::auto_ptr的出现,主要是为了解决“被异常抛出时发生资源泄漏”的问题。即如果我们让资源在局部对象构造时分配,在局部对象析构时释放。这样即使在函数执行过程时发生异常退出,也会因为异常能保证局部对象被析构从而保证资源被释放。std::auto_ptr就是基于这个理念而设计。
std::auto_ptr是摧毁式的拷贝,使用它要非常小心。误用摧毁式拷贝会对程序的正确性、数据和脑细胞带来毁灭性后果。
std::auto_ptr与Boost::scoped_ptr的不同在于,std::auto_ptr有残缺的拷贝语义:所有权转移。我们用VS 2003的源码来分析std::auto_ptr设计和实现。为了便于阅读,删除了一些函数,重新进行了排版。
template<class _Ty> ass auto_ptr{ public: typedef _Ty element_type; explicit auto_ptr(_Ty *_Ptr = 0) _THROW0(): _Myptr(_Ptr) { // construct from object pointer } auto_ptr(auto_ptr<_Ty>& _Right) _THROW0(): _Myptr(_Right.release()) { // construct by assuming pointer from _Right auto_ptr } auto_ptr(auto_ptr_ref<_Ty> _Right) _THROW0(): _Myptr(_Right._Ref.release()) { // construct by assuming pointer from _Right auto_ptr_ref } auto_ptr<_Ty>& operator=(auto_ptr<_Ty>& _Right) _THROW0() { // assign compatible _Right (assume pointer) reset(_Right.release()); return (*this); } auto_ptr<_Ty>& operator=(auto_ptr_ref<_Ty>& _Right) _THROW0() { // assign compatible _Right._Ref (assume pointer) reset(_Right._Ref.release()); return (*this); } ~auto_ptr(){ // destroy the object delete _Myptr; } _Ty& operator*() const _THROW0(){ // return designated value return (*_Myptr); } _Ty *operator->() const _THROW0(){ // return pointer to class object return (&**this); } _Ty *get() const _THROW0(){ // return wrapped pointer return (_Myptr); } _Ty *release() _THROW0(){ // return wrapped pointer and give up ownership _Ty *_Tmp = _Myptr; _Myptr = 0; return (_Tmp); } void reset(_Ty* _Ptr = 0){ // destroy designated object and store new pointer if (_Ptr != _Myptr) delete _Myptr; _Myptr = _Ptr; } private: _Ty *_Myptr; // the wrapped object pointer };
std::auto_ptr的拷贝构造函数和赋值函数,都调用了_Right.release()。它将拷贝源设置为0(NULL),然后将对象指针赋值给新的std::auto_ptr。由于存在所有权转移,在多重赋值时要特别小心。将std::auto_ptr通过值或引用传递给函数时,也非常危险(常引用传递给函数是安全的)。
需要注意的是,拷贝构造函数auto_ptr(auto_ptr<_Ty>& _Right)的参数是引用(reference)类型,而不是常引用(const reference)类型。为什么呢?std::auto_ptr有所有权转移,会修改拷贝源,必定是引用类型。但是这带来一个问题,如何利用右值来初始化std::auto_ptr呢?
C++中有左值和右值之分,函数如果有返回值,那么是r-value。右值作为reference函数参数时,只能是const reference。到现在为止,std::auto_ptr可以拷贝和赋值non-const的std::auto_ptr和禁止拷贝和赋值const的std::auto_ptr的功能,只是无法拷贝和赋值临时的std::auto_ptr (右值)。为了解决这个问题,std::auto_ptr引入了一个“额外的间接层” auto_ptr_ref,来完成一个从r-value到l-value之间的过渡。
template<class _Ty>class auto_ptr; template<class _Ty> struct auto_ptr_ref { // proxy reference for auto_ptr copying auto_ptr_ref(auto_ptr<_Ty>& _Right): _Ref(_Right) { // construct from compatible auto_ptr } auto_ptr<_Ty>& _Ref; // reference to constructor argument };
一个实例如下:
std::auto_ptr<int> ap1 = std::auto_ptr<int>(new int(0));
std::auto_ptr<int>(new int(0))是一个临时对象,一个右值,一般的拷贝构造函数当然能拷贝右值,因为其参数类别为一个const reference,但是我们知道,std::auto_ptr的拷贝函数其参数类型为reference。所以,为了使这行代码能通过,我们引入auto_ptr_ref来实现从右值向左值的转换。其过程为:
1. ap1要通过拷贝 auto_ptr<int>(new int(0))来构造自己;
2. auto_ptr<int>(new int(0))作为右值与现有的两个拷贝构造函数参数类型都无法匹配,也无法转换成该种参数类型;
3. 发现辅助的拷贝构造函数auto_ptr(auto_ptr_ref<T> rhs) throw();
4. 试图将auto_ptr<int>(new int(0))转换成auto_ptr_ref<T>;
5. 发现类型转换函数operator auto_ptr_ref<Y>() throw(),转换成功,从而拷贝成功。
通过5步,从而通过一个间接类成功的实现了拷贝构造右值(临时对象)。这个辅助方法不会使const auto_ptr被拷贝,原因是在第5步,此类型转换函数为non-const的,我们知道,const对象是无法调用non-const成员的,所以转换失败。可以看出,右值和const还是存在差别的。左值和右值分别是:
· 左值是可以出现在等号(=)左边的值,右值是只能出现在等号右边的值;
· 左值是可读可写的值,右值是只读的值;
· 左值有地址,右值没有地址。
与Boost::scoped_ptr没有拷贝语义不同,std::auto_ptr有拷贝语义,但同样不能用于STL的容器。为什么呢?C++标准规定,STL元素必须具备拷贝构造和可赋值的特征。std::auto_ptr虽然有拷贝构造函数和赋值函数,但是它们存在所有权转移。拷贝源进行拷贝后,被赋值为NULL。这与STL定义的拷贝语义不服。
由于std::auto_ptr的构造函数被定义为explicit,因此也不能通过赋值来初始化std::auto_ptr。C++的应用中,应该减少隐式调用,因为它会产生许多意料不到的潜在转换,是万恶之源。
Boost::shared_ptr/std::tr1::shared_ptr
与Boost::scoped_ptr 一样,shared_ptr 被指向对象也保证会被删除,但不同的是,这将发生在最后一个指向它的shared_ptr 被销毁时,或是调用reset 方法时。shared_ptr符合C++标准库的“可复制构造”(CopyConstructible)和“可赋值”(Assignable)要求,所以可被用于标准的库容器中。另外它还提供了比较操作符,所以可与标准库的关联容器一起工作。
shared_ptr 模板的实现采用了引用计数技术, 有无法正确处理循环引用的情况。例如,如果 main() 持有一个指向 A 的 shared_ptr, A 又直接或间接持有一个指回 A 的 shared_ptr,A 的使用计数是 2。最初的 shared_ptr 析构后将导致一个使用计数为 1 的 A 被悬挂。我们可以使用weak_ptr 来打破循环。
shared_ptr 模板还是线程安全的,这点在多线程程序中也非常重要。shared_ptr 对象提供与内建类型一样的线程安全级别。一个 shared_ptr 实例可以同时被多个线程“读”(仅使用不变操作进行访问)。不同的 shared_ptr 实例可以同时被多个线程“写入” (使用类似 operator= 或 reset 这样的可变操作进行访问)。任何其它的同时访问的结果会导致未定义行为。shared_ptr保证了引用计数修改的原子性,在不同的平台上采用了不同的同步机制: 针对linux,unix平台使用pthread接口进行同步的;针对windows平台,使用InterlockedIncrement, InterlockedDecrement机制;针对AMD64硬件平台的,内部使用汇编指令实现了atomic_increment,atomic_decrement。
shared_ptr 和它的大多数成员函数对于 模板参数T 没什么要求,允许它是一个不完整类型,或者为 void。意味着,可以使用前置声明,进而解除与T的接口的强耦合。
只要 T* 能被隐式地转换到 U*,则 shared_ptr<T> 就能被隐式地转换到 shared_ptr<U>。特别是,shared_ptr<T> 隐式转换到 shared_ptr<T const>(申明的限定部分各个限定词之间的前后顺序没关系,因此T const与const T是一样的。推荐使用T const的方式,因为理解常量和指针问题上非常直观),当 U 是 T 的一个可访问基类的时候,还能转换到 shared_ptr<U>,以及转换到 shared_ptr<void>。
shared_ptr的实现包含四个类:
template<class T> class shared_ptr { public: typedef T element_type; shared_ptr(); // never throws template<class Y> explicit shared_ptr(Y * p); template<class Y, class D> shared_ptr(Y * p, D d); template<class Y, class D, class A> shared_ptr(Y * p, D d, A a); ~shared_ptr(); // never throws shared_ptr(shared_ptr const & r); // never throws template<class Y> shared_ptr(shared_ptr<Y> const & r); // never throws template<class Y> explicit shared_ptr(weak_ptr<Y> const & r); template<class Y> explicit shared_ptr(std::auto_ptr<Y> & r); shared_ptr & operator=(shared_ptr const & r); // never throws template<class Y> shared_ptr & operator=(shared_ptr<Y> const & r); // never throws template<class Y> shared_ptr & operator=(std::auto_ptr<Y> & r); void reset(); // never throws template<class Y> void reset(Y * p); template<class Y, class D> void reset(Y * p, D d); template<class Y, class D, class A> void reset(Y * p, D d, A a); template<class Y> void reset(shared_ptr<Y> const & r, T * p); // never throws T & operator*() const; // never throws T * operator->() const; // never throws T * get() const; // never throws bool unique() const; // never throws long use_count() const; // never throws operator unspecified-bool-type() const; // never throws void swap(shared_ptr & b); // never throws };
shared_ptr允许定制分配器(Allocator)和释放器(Deallocator)。D 必须是 CopyConstructible(可拷贝构造)的。D 的拷贝构造函数和析构函数不能抛出异常。表达式 d(p) 必须是正常可用的,不能发生未定义行为,也不能抛出异常。A 必须是一个 Allocator(分配器),关于分配器的描述参考C++标准。
shared_ptr可以通过weak_ptr和auto_ptr构造。weak_ptr模板类存储的是由shared_ptr 管理的对象的一个弱引用。弱引用的意思是,它并不维护对象的引用计数。weak_ptr只有通过shared_ptr才能构造出来。在后面我们会详细讨论它。
template<class Y> explicit shared_ptr(weak_ptr<Y> const & r): pn(r.pn) // may throw { // it is now safe to copy r.px, as pn(r.pn) did not throw px = r.px; } template<class Y> shared_ptr( weak_ptr<Y> const & r, boost::detail::sp_nothrow_tag ): px( 0 ), pn( r.pn, boost::detail::sp_nothrow_tag() ) // never throws { if( !pn.empty() ) { px = r.px; } }
这是两个不同的版本,后一版本通过调用不同shared_count,保证不跑出异常。shared_ptr可以通过weak_ptr构造时,没有增加引用计数,只是将weak_ptr托管的指针和weak_ptr的计数器拷贝过来。
template<class Y> explicit shared_ptr(std::auto_ptr<Y> & r): px(r.get()), pn() { Y * tmp = r.get(); pn = boost::detail::shared_count(r); boost::detail::sp_enable_shared_from_this( this, tmp, tmp ); }
如果传给shared_ptr构造函数的是一个std::auto_ptr,首先初始化对象指针和引用计数(初值为0),然后对引用计数重新赋值,构造shared_count会调用std::auto_ptr的release函数,释放std::auto_ptr的拥有权,最后一步是实现enable_shared_from_this功能的。enable_shared_from_this用来使用shared_ptr访问自己,可以返回一个指向自身的shared_ptr。内部实现方式是采用weak_ptr来弱引用自身。
#if defined( BOOST_HAS_RVALUE_REFS ) // ... except in C++0x, move disables the implicit copy shared_ptr( shared_ptr const & r ): px( r.px ), pn( r.pn ) // never throws { } shared_ptr( shared_ptr && r ): px( r.px ), pn() // never throws { pn.swap( r.pn ); r.px = 0; } #endif
在支持右值引用的编译器上,Boost会定义上面的两个函数。第二个是标准的右值引用,第一个会使常量引用也能够构造shared_ptr,为什么要开启呢?支持Const r-value?
Boost::weak_ptr/std::tr1::weak_ptr
在需要访问shared_ptr 对象,而又不想改变其引用计数的情况下,可以使用weak_ptr。
weak_ptr存储由shared_ptr 管理的对象的弱引用。要访问weak_ptr 所指向的对象,可以使用shared_ptr 构造器或make_shared 函数来将weak_ptr 转换为shared_ptr。指向被管理对象的最后一个shared_ptr 被销毁时将删除该对象,即使仍有weak_ptr 指向它也是如此。与原始指针不同的是,届时最后一个shared_ptr 会检查是否有weak_ptr 指向该对象,如果有的话就将这些weak_ptr 置为空。这样就不会发生使用原始指针时可能出现的悬吊指针(dangling pointer)情况,从而获得更高的安全水平。
weak_ptr 符合C++标准库的“可复制构造”(CopyConstructible)和“可赋值”(Assignable)要求,所以可被用于标准的库容器中。另外它还提供了比较操作符,所以可与标准库的关联容器一起工作。
template<class T> class weak_ptr { public: typedef T element_type; weak_ptr(); template<class Y> weak_ptr(shared_ptr<Y> const & r); weak_ptr(weak_ptr const & r); template<class Y> weak_ptr(weak_ptr<Y> const & r); ~weak_ptr(); weak_ptr & operator=(weak_ptr const & r); template<class Y> weak_ptr & operator=(weak_ptr<Y> const & r); template<class Y> weak_ptr & operator=(shared_ptr<Y> const & r); long use_count() const; bool expired() const; shared_ptr<T> lock() const; void reset(); void swap(weak_ptr<T> & b); };
weak_ptr是对shared_ptr的弱引用,构造一个weak_ptr需要一个shared_ptr。当然,也可以构造空的weak_ptr和引用weak_ptr的weak_ptr(内部维护了一个weak_count,它可以被友员shared_count访问,从而避免释放shared_ptr造成weak_ptr悬空)。
weak_ptr( shared_ptr<Y> const & r ): px( r.px ), pn( r.pn ){} // never throws weak_ptr( weak_ptr const & r ): px( r.px ), pn( r.pn ){} // never throws
从weak_ptr的构造函数可以看出,它只是拷贝了对象指针和引用计数,并没有增加引用的计数。
shared_ptr<T> lock() const // never throws { return shared_ptr<element_type>( *this, boost::detail::sp_nothrow_tag() ); } void reset() // never throws in 1.30+ { this_type().swap(*this); }
weak_ptr的lock函数构造一个shared_ptr并返回,构造过程中通过shared_count的add_ref_lock来增加引用计数。reset函数构造一个空的weak_ptr然后做一次置换,从而实现重置的目的。
Boost::intrusive_ptr
Boost::intrusive_ptr 类模板存储一个指向带有侵入式引用计数的对象的指针。每一个新的Boost::intrusive_ptr 实例都通过对函数intrusive_ptr_add_ref 的无条件调用(将指针作为参数)增加引用计数。同样,当一个Boost::intrusive_ptr 被销毁,它会调用intrusive_ptr_release,这个函数负责当引用计数降为 0 时销毁这个对象。这两个函数的适当定义由用户提供。在支持 argument-dependent lookup (参数依赖查找)的编译器上,intrusive_ptr_add_ref 和 intrusive_ptr_release 应该和它们的参数定义在同一个名字空间中,否则,就定义名字空间 boost 中。
这个类模板以 T 为参数,T 是被指向的对象的类型。只要 T* 能被隐式地转换到 U*,则Boost::intrusive_ptr<T> 就能被隐式地转换到Boost:: intrusive_ptr<U>。使用Boost::intrusive_ptr 的主要原因是:
template<class T> class intrusive_ptr { public: typedef T element_type; intrusive_ptr(); // never throws intrusive_ptr(T * p, bool add_ref = true); intrusive_ptr(intrusive_ptr const & r); template<class Y> intrusive_ptr(intrusive_ptr<Y> const & r); ~intrusive_ptr(); intrusive_ptr & operator=(intrusive_ptr const & r); template<class Y> intrusive_ptr & operator=(intrusive_ptr<Y> const & r); template<class Y> intrusive_ptr & operator=(T * r); void reset(T * r); T & operator*() const; // never throws T * operator->() const; // never throws T * get() const; // never throws operator unspecified-bool-type() const; // never throws void swap(intrusive_ptr & b); // never throws };
Boost::intrusive_ptr的定义还是比较清晰的,需要注意的是它的构造函数和析构函数:
intrusive_ptr( T * p, bool add_ref = true ): px( p ) { if( px != 0 && add_ref ) intrusive_ptr_add_ref( px ); } ~intrusive_ptr() { if( px != 0 ) intrusive_ptr_release( px ); }
Boost::intrusive_ptr需要用户定义intrusive_ptr_add_ref和intrusive_ptr_release。如果没有特殊的需要,不推荐大家使用Boost::intrusive_ptr。