原文:http://blog.csdn.net/csfreebird/article/details/8551369
shared_ptr
shared_ptr源自于著名的boost库,智能指针自从Scott Meyers在其作品<<More Effective C++>>首次提出并给出一个示范版本后得到了很大的发展。Loki和Boost是影响广泛的两个,Boost的智能指针较易使用,而Loki以功能强大思想深邃著称于世。C++标准委员会的人虽然对Loki赞赏有加,但是易用性似乎占了上风,于是Boost中的shared_ptr和weak_ptr出现在了C++标准的修改版中。现在可以肯定的是,C++09标准中一定会出现的是shared_ptr和weak_ptr两个指针。
下面的关于shared_ptr的介绍,摘自马维达几年前的一篇文章《智能指针的标准之争:Boost vs. Loki》:
shared_ptr:意在用于对被指向对象的所有权进行共享。被指向对象也保证会被删
除,但是这将发生在最后一个指向它的shared_ptr 被销毁时,或是调用reset 方法时。 shared_ptr符合C++标准库的“可复制构造”(CopyConstructible)和“可赋值”(Assignable)要求,所以可被用于标准的库容器中。另外它还提供了比较操作符,所以可与标准库的关联容器一起工作。shared_ptr 不能用于存储指向动态分配的数组的指针,这样的情况应该使用shared_array。该模板的实现采用了引用计数技术,所以无法正确处理循环引用的情况。可以使用weak_ptr 来“打破循环”。shared_ptr 还可在多线程环境中使用。
下面的例子演示了如何在vector中使用shared_ptr:
class CTest
{
...
};
typedef boost::shared_ptr<CTest> TestPtr;
void PT(const TestPtr &t)
{
std::cout << "id: " << t->GetId() << "/t/t";
std::cout<< "use count: " << t.use_count() << '/n';
}
void main()
{
std::vector<TestPtr> TestVector;
TestPtr pTest0(new CTest(0));
TestVector.push_back(pTest0);
TestPtr pTest1(new CTest(1));
TestVector.push_back(pTest1);
TestPtr pTest2(new CTest(2));
TestVector.push_back(pTest2);
std::for_each(TestVector.begin(), TestVector.end(), PT);
std::cout << '/n';
pTest0.reset();
pTest1.reset();
pTest2.reset();
std::for_each(TestVector.begin(), TestVector.end(), PT);
std::cout << '/n';
TestVector.clear();
std::cout << '/n';
std::cout << "exiting.../n";
}
其运行结果为:
id: 0 use count: 2
id: 1 use count: 2
id: 2 use count: 2
id: 0 use count: 1
id: 1 use count: 1
id: 2 use count: 1
id: 0 - Destructor is being called
id: 1 - Destructor is being called
id: 2 - Destructor is being called
exiting...
运行结果中的“use count”是通过shared_ptr 的use_count()方法获得的“使用计数”,也就是,对所存储指针进行共享的shared_ptr 对象的数目。我们可以看到,在通过new 分配了3 个CTest 对象,并将相应的shared_ptr 对象放入TestVector 后,三个使用计数都为2;而在我们使用reset()方法复位pTest0、pTest1 和pTest2 后,TestVector 中的各个shared_ptr 对象的使用计数变成了1。这时,我们调用TestVector的clear()方法清除它所包含的shared_ptr 对象;因为已经没有shared_ptr 对象再指向我们先前分配的3
个CTest 对象,这3 个对象也随之被删除,并导致相应的析构器被调用
有意思的是,虽然使用者对于支持Loki还是Boost分歧很大,他们的作者倒是和平相处,在Boost的网站上,我们可以看到Andrei推荐使用Boost,而在Boost的智能指针文档中,我们看到Boost对喜爱基于策略的智能指针的程序员推荐看看Andrei的大作<<Modern C++ Design>>。
shared_ptr是基于引用计数的智能指针,那么多个对象如何共享引用计数呢?
在前面的浅拷贝和深拷贝一节中,我曾经给出一个很简单的例子,使用了静态成员变量来统计引用计数。
所有A类的对象共享一个变量_ref,这是一个简易装置,以至于在现实中几乎不可用。假如A的两个对象共享同一个字符串指针char* _p,并且使用_ref管理该指针的引用计数,那么现在又有两个A对象共享另外的字符串指针,他们用什么变量来管理引用计数呢?
现实世界中有两个解决方案,有一种string内部使用这样的引用计数器:
如果string对象要存储的是一个”hello,world”,一共占据11个字节空间,内部分配逻辑类似如下代码:
char* _pData=new char[12];
++_pData;
这样,_pData前面的一个字节将表示引用计数(有可能是4个字节),_pData仍然表示字符串的起始位置。这样两组string对象互不干扰,每组都有一个引用计数器。
shared_ptr的做法复杂点,请看下图:
shared_ptr<T>内部有两个成员变量,一个是裸指针px,一个是管理引用计数的变量pn,类型是shared_count。
- T * px; // contained pointer
- boost::detail::shared_count pn; // reference counter
shared_count类有一个成员变量pi_,是一个sp_counted_base类型的指针,实际上指向sp_counted_impl_p类对象。
- sp_counted_base * pi_;
当shared_count对象构造时,将创建sp_counted_impl_p对象。下面的代码我去掉了一些宏:
template<class Y>
explicit shared_count( Y * p ): pi_( 0 )
{
try
{
pi_ = new sp_counted_impl_p<Y>( p );
}
catch(...)
{
boost::checked_delete( p );
throw;
}
}
sp_counted_base类的拥有成员变量use_count,并且在默认构造函数中初始化为1。
- sp_counted_base(): use_count_( 1 ), weak_count_( 1 )
- {
- }
当第二个shared_ptr<T>对象根据第一个对象创建时,使用编译器默认提供的拷贝构造函数,作为shared_ptr类的成员变量shared_count pn的拷贝构造函数因此被调用。
shared_count(shared_count const & r): pi_(r.pi_) // nothrow
{
if( pi_ != 0 ) pi_->add_ref_copy();
}
我们可以看到引用计数器被增加了1。析构管理不再详述。
这里需要仔细画图来表达清楚。第一张图描述了创建一个shared_ptr对象的内存分配细节:
第二张图描述了用一个已经存在的shared_ptr对象构造一个复本的时候的内存分配细节。
你也可以使用shared_count类作为自己的引用计数管理器,名字空间为boost::detail。注意shared_count类是模板类,构造时需要传递你的T* p给它。
补充:__shared_count内部的use_count主要用来标记被管理对象的生命周期,weak_count主要用来标记管理对象的生命周期。