(一)Boost库中,智能指针并不只shared_ptr一个。同族但有不同的功能目标的还有如下5个:
scoped_ptr
scoped_array
shared_ptr
shared_array
weak_ptr
scoped_ptr、scoped_array与标准C++中的智能指针auto_ptr功能基本类似,不过它不传递所有权,不可复制。它不能共享所有权的特性却大大限制了其使用范围,而boost::shared_ptr可以解决这一局限。顾名思义,boost::shared_ptr是可以共享所有权的智能指针,其主要目标就是在小范围,小作用域中使用,以减少显式的delete, new配对操作,提高代码的安全性。scoped_ptr是针对指针的版本,而scoped_array同是专门针对数组的。shared_array、shared_ptr它们在所有对象中,共享所指向实体的所有权。即只要是指向同一个实体对象的shared_ptr对象,都有权操作这个对象,并根据自己产生新的对象,并把所有权共享给新的对象。即它是满足STL对对象的基本要求可复制,可赋值的。可以与所有的STL容器,算法结合使用。顾名思义, shared_ptr是针对任意类型的指针的,而shared_array则是专门针对任意类型的数组的。
boost::shared_ptr的解决方案:
上面我们分析了shared_ptr问题的产生,以及可能的实现方式和应该注意的问题。下面我们来看看boost::shared_ptr是如何实现上面的思想的。
1.文件结构
定义boost::shared_ptr主要涉及到以下文件
shared_ptr.hpp
detail/shared_count.hpp
detail/sp_counted_base.hpp
detail/sp_counted_base_pt.hpp
detail/sp_counted_base_win32.hpp
detail/sp_counted_base_nt.hpp
detail/sp_counted_base_pt.hpp
detail/sp_counted_base_gcc_x86.hpp
...
detail/sp_counted_base_impl.hpp
涉及的类主要有以下几个:
shared_ptr
shared_count
sp_counted_base
sp_counted_base_impl
其中
shared_ptr定义在shared_ptr.hpp中
share_count定义在shared_count.hpp中
sp_counted_base定义在sp_counted_base_XXX.hpp中。
XXX指代针对特定平台的实现。
sp_counted_base_impl定义在sp_counted_base_impl.hpp中
2.类功能
sp_counted_base 是问题4中,记数器的实现。
针对不同的平台使用了不同的同步机制。如pt是针对linux, unix平台使用pthread接口进行同步的。
win32是针对windows平台,使用InterlockedIncrement, InterlockedDecrement机制。
gcc_x86是针对AMD64硬件平台的,内部使用汇编指令实现了atomic_increment,
atomic_decrement。
sp_counted_base_impl是个模板,它继承自sp_counted_base,
主要实现了父类中一个纯虚函数dispose。具体的由它来负责在记数值到0(即没有代理时)释放所托管的资源。
shared_count是个类模板。它存在的意义在于和代理类shared_ptr同生共死,
在构造函数中生成记数器,在代理的传递过程中驱动记数器增减。
shared_ptr是个类模板。它是托管资源的代理类,所有对资源的操作,
访问都通过它来完成。
3.类详细介绍
shared_ptr 类模板。 它是我们直接使用的代理类。
两个属性
pn : shared_count 记数器类。 shared_ptr完全操作pn的生命期,
在构造时构造它,在析构时自动析构它。
这些都是利用构造与析构函数的特性自动完成的。
template<class Y>
explicit shared_ptr( Y * p ): px( p ), pn( p ) // Y must be
complete
{
detail::sp_enable_shared_from_this( pn, p, p );
}
px : T* 它是代理的资源的指针。 所有针对资源的操作*,
->都直接使用它来完成。
reference operator* () const // neverthrows
{
BOOST_ASSERT(px != 0);
return *px;
}
T * operator-> () const // never throws
{
BOOST_ASSERT(px != 0);
return px;
}
两个方法:
operator=完成代理的赋值传递。
通过这样,就可以有多个代理同时托管同一个资源。在众多的代理中, 它们共享所托管资源的操作权。
shared_ptr & operator=(shared_ptrconst & r) // never throws
{
px = r.px;
pn = r.pn; // shared_count::op=doesn't throw
return *this;
}
T* get()资源原始位置的获取。通过这个方法,我们可以直接访问到资源,
并可以对它进行操作。
T * get() const // never throws
{
return px;
}
user_count返回当前资源的代理个数,即有多少个对些资源的引用。
long use_count() const // neverthrows
{
return pn.use_count();
}
为了方便操作,并完全模拟原生指针的行为,boost::shared_ptr还定义了大量的其它操作函数。
shared_count类 记数器的包装。
一个属性:
pi : sp_counted_base *它指向真正的记数器。
构造函数:
template<class Y> explicitshared_count( Y * p ): pi_( 0 )
#ifdefined(BOOST_SP_ENABLE_DEBUG_HOOKS)
, id_(shared_count_id)
#endif
{
#ifndef BOOST_NO_EXCEPTIONS
try
{
pi_ = newsp_counted_impl_p<Y>( p );
}
catch(...)
{
boost::checked_delete( p );
throw;
}
#else
pi_ = new sp_counted_impl_p<Y>( p);
if( pi_ == 0 )
{
boost::checked_delete( p );
boost::throw_exception(std::bad_alloc() );
}
#endif
}
当使用一个资源指针来构造一个shared_count时,它知道针对此资源要生成一个代理。
所以生成一个记数器pi。如果在构造记数器的过程中出现任何异常行为,即记数器资源的初始化未成功完成时, 就释放掉资源。
(这就是资源申请即初始化, 对于一个资源管理类来说,要不所有资源申请成功, 要不构造失败)
operator=赋值函数。
在shared_ptr被赋值的时候,会调用它。经过复制后一个shared_ptr变成两个, 所以要对记数器进行增加。
同时如果被赋值的代理原有托管的资源将被释放。
shared_count & operator=(shared_count const & r) // nothrow
{
sp_counted_base * tmp = r.pi_;
if( tmp != pi_ )
{
if( tmp != 0 )tmp->add_ref_copy();
if( pi_ != 0 )pi_->release();
pi_ = tmp;
}
return *this;
}
析构函数析构函数在shared_ptr超出作用域被析构时自动调用。
每析构一个shared_ptr,则代理数就少一个, 所以调用记数器的release函数,
减少记数值。
~shared_count() // nothrow
{
if( pi_ != 0 ) pi_->release();
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
id_ = 0;
#endif
}
sp_counted_base 记数器类。
这是一个普通类,没有被模板化。它定义了记数器公用的所有操作, 并实现了同步。
两个属性
use_count记录当前的代理数目。
mutex互斥锁(在不同平台的实现中,是不同的类型。以linux为例,
是pthread_mutex_t)
构造函数初始化互斥锁和记数
sp_counted_base(): use_count_( 1 ),weak_count_( 1 )
{
#if defined(__hpux) &&defined(_DECTHREADS_)
pthread_mutex_init( &m_,pthread_mutexattr_default );
#else
pthread_mutex_init( &m_, 0 );
#endif
}
析构函数释放互斥锁
virtual ~sp_counted_base() // nothrow
{
pthread_mutex_destroy( &m_ );
}
同步增加记数值,即根据原有的复制构造出或通过赋值产生新的代理时记数加1
void add_ref_copy()
{
pthread_mutex_lock( &m_ );
++use_count_;
pthread_mutex_unlock( &m_ );
}
释放函数,在代理超界被析构时使用。 它首先减少引用记数,
然后查看记数值,如果为0, 则调用dispose释放资源。并销毁掉本身
void release() // nothrow
{
pthread_mutex_lock( &m_ );
long new_use_count =--use_count_;
pthread_mutex_unlock( &m_ );
if( new_use_count == 0 )
{
dispose();
weak_release();
}
}
释放托管资源的函数。 为纯虚函数, 要求在子类中实现。
只所以不同这个类中实现,是由于要托管的类型是未知的,如果要实现则要记录这个资源,则此类不得不模板化。
将此功能分离到子类中的好处就非常明显。子类可以直接利用父类的功能,只要模板化一下,实现一个函数就行。
这样会大大加快编译速度。
virtual void dispose() = 0; // nothrow
销毁函数。 在shared_count中的pi即记数器是New出来的。
所以在适当的时候要销毁掉。 而这个时机就是资源被释放的时候。
即记数器完成了对资源的管理, 同时完成了对自身的管理。
// destroy() is called whenweak_count_ drops to zero.
virtual void destroy() // nothrow
{
delete this;
}
sp_counted_base_impl 类模板。
一个属性
px: T*记录各种类型的资源。
实现了父类的dispose函数。 实现对资源的释放。
virtual void dispose() // nothrow
{
#ifdefined(BOOST_SP_ENABLE_DEBUG_HOOKS)
boost::sp_scalar_destructor_hook(px_, sizeof(X), this );
#endif
boost::checked_delete( px_ );
}
工作流程:
1用户申请一个资源p
p = new Something();
2 用户将资源p托管给shared_ptr;
shared_ptr(p);
2.1 shared_ptr 构造函数调用,构造出记数对象shared_count
shared_ptr() : px(p), pn(p)
2.1.1 shared_count 构造函数调用,构造出户数器对象
pi = newsp_counted_base_impl(p);
首先让我们通过一个例子看看shared_ptr的基本用法:
#include <string>
#include <iostream>
#include <boost/shared_ptr.hpp>
class implementation
{
public:
~implementation() { std::cout <<"destroyingimplementation\n"; }
void do_something(){ std::cout << "did something\n"; }
};
void test()
{
boost::shared_ptr<implementation> sp1(new implementation());
std::cout<<"The Samplenow has "<<sp1.use_count()<<" references\n";
boost::shared_ptr<implementation> sp2 = sp1;
std::cout<<"The Samplenow has "<<sp2.use_count()<<" references\n";
sp1.reset();
std::cout<<"AfterReset sp1. The Sample now has "<<sp2.use_count()<<"references\n";
sp2.reset();
std::cout<<"AfterReset sp2.\n";
}
void main()
{
test();
}
该程序的输出结果如下:
The Sample nowhas 1 references
The Sample now has 2 references
After Reset sp1. The Sample now has 1 references
destroying implementation
After Reset sp2.
可以看到,boost::shared_ptr指针sp1和sp2同时拥有了implementation对象的访问权限,且当sp1和sp2都释放对该对象的所有权时,其所管理的的对象的内存才被自动释放。在共享对象的访问权限同时,也实现了其内存的自动管理。
boost::shared_ptr的内存管理机制:
boost::shared_ptr的管理机制其实并不复杂,就是对所管理的对象进行了引用计数,当新增一个boost::shared_ptr对该对象进行管理时,就将该对象的引用计数加一;减少一个boost::shared_ptr对该对象进行管理时,就将该对象的引用计数减一,如果该对象的引用计数为0的时候,说明没有任何指针对其管理,才调用delete释放其所占的内存。
上面的那个例子可以的图示如下:
1. sp1对implementation对象进行管理,其引用计数为1
2. 增加sp2对implementation对象进行管理,其引用计数增加为2
3. sp1释放对implementation对象进行管理,其引用计数变为1
4. sp2释放对implementation对象进行管理,其引用计数变为0,该对象被自动删除
boost::shared_ptr的特点:
和前面介绍的boost::scoped_ptr相比,boost::shared_ptr可以共享对象的所有权,因此其使用范围基本上没有什么限制(还是有一些需要遵循的使用规则,下文中介绍),自然也可以使用在stl的容器中。另外它还是线程安全的,这点在多线程程序中也非常重要。
boost::shared_ptr的使用规则:
boost::shared_ptr并不是绝对安全,下面几条规则能使我们更加安全的使用boost::shared_ptr:
1. 避免对shared_ptr所管理的对象的直接内存管理操作,以免造成该对象的重释放
2. shared_ptr并不能对循环引用的对象内存自动管理(这点是其它各种引用计数管理内存方式的通病)。
参考文章:http://www.cnblogs.com/TianFang/archive/2008/09/19/1294521.html