最近在看陈硕的《Linux多线程服务端编程:使用muduo C++网络库》,第一章中重点讲了多线程下指针的处理问题,其中重点是 shared_ptr 与 weak_ptr, 但书中对这部分的基础介绍比较简略,以前看过智能指针,但一直没有完整学习过,因而学习翻译了官方的文档进行学习。以下是 boost::shared_ptr 的中文翻译,其中一部分感觉不是很重要的这里暂时没有给出翻译。
Introduction
shared_ptr类模版存储了如用 C++ 的 new 表达式动态分配的对象的指针。当最后一个指向该对象的 share_ptr 被销毁或者重置时,该对象被保证一定会被删除。
例如:1
2shared_ptr< X > p1( new X );
shared_ptr< void > p2( new int(5) );
销毁时,不论模版的参数为何,shared_ptr 将删除在构造时所传递真实类型的指针。在第二个例子中,尽管 p2 类型是 shared_ptr< void >, 存储了一个类型为void*的指针, 当 p2 被销毁或者被重置,它将对传递到构造函数的 int* 调用delete函数。
基本成员函数
shared_ptr 拥有拷贝构造函数,Move构造函数,拷贝赋值函数,Move赋值函数,并且能用在标准库的容器里。对比操作符同样也提供,使得 shared_ptr 能用在标准库中的关联容器里。(关联容器?)
循环嵌套问题
因为使用了引用计数来作为其实现,因此循环嵌套 shared_ptr 的实体将不会被回收。 例如,如果 main() 有一个指向对象 A 的 shared_ptr,而 A 自身又直接或者间接地拥有一个指向 A 的 shared_ptr,那么 A 的引用计数将为 2。销毁 main() 中的 shared_ptr 将使得 A 的引用计数为 1。(使用 weak_ptr 打破循环?)
void 模版参数
shared_ptr 使用 T 来表明所指向的对象的类型,它及它的很多成员函数并不需要使用到 T;它被允许是一个不完整的类型,或者 void,需要使用到 T 的成员函数将在下面文档中介绍。
转换
shared_ptr< T > 能被隐式地转换到 shared_ptr< U >,T* 能被隐式转换到 U*。特别地, shared_ptr< T > 能被隐式转换为 shared_ptr< T const >;当 U 是 T 的基类时, 能被转换到 shared_ptr< U >;转换到 shared_ptr< void >。
std::shared_ptr
shared_ptr 是 C++11 一部分,为 std::shared_ptr。
数组指针
从 Boost 1.53 开始,shared_ptr 能够保存指向动态分配数组的指针,模版参数为 T[] 或者 T[N],两者间几乎没什么差别,后者能够检查进行下标的范围。
例如:1
2shared_ptr< double[ 1024 ] > p1( new double[ 1024 ])
shared_ptr< double[] > p2( new double[n] )
Best practices
去除内存泄漏的一个简单方法是:总是使用一个智能指针去存储 new 的结果。每个 new 关键字出现的地方应该有如下的形式:1
shared_ptr< T > p( new Y );
这里 shared_ptr 可以用其他以外的智能指针代替; T 和 Y 可以是相同的类型,Y 的构造函数也可以作为传递的参数。
如果你仔细观察,你发现这里不需要使用 delete 语句,try/catch 也很少。
避免使用未命名的临时 shared_ptr 节省代码书写,为说明这个问题,考虑下面这个例子:1
2
3
4
5
6
7
8
9
10
11void f( shared_ptr< int >, int);
int g();
void ok()
{
shared_ptr< int > p( new int(2) );
f( p, g() );
}
void bad()
{
f( shared_ptr< int >( new int(2)), g() );
}
ok函数使用了比较好的方式,而 bad 函数使用了临时的 shared_ptr,导致了内存泄漏的可能。因为函数参数的计算没有指定顺序,可能 new int(2) 会首先执行,然后 g(),但如果函数 g 在执行是抛出了异常,那么我们将无法调用 shared_ptr 的构造函数。 链接 Herb Sutter’s treatment (及here) 对这个问题有更多的说明。
上述描述的异常安全问题也能通过使用 make_shared 或者 allocate_shared 工厂函数被排除(定义在 boost/make_shared.hpp)。这些工厂函数通过整合内存加快了程序的效率。
Synopsis
1 | namespace boost { |
Members
element_type
1 | typedef ... element_type; |
当 T 不是数组类型时,element_type 是 T;当 T 是 U[] 或者 U[N] 时,element_type 是 U
默认构造函数
1 | shared_ptr(); // never throws |
效果:创建一个空的 shared_ptr。
后置条件:use_count() == 0 && get() == 0。
抛出:无。
保证 nothrows 是重要的,因为 reset() 通过默认构造函数来指定,这也表明了默认构造函数不能分配内存空间。
指针构造函数
1 | template<class Y> explicit shared_ptr(Y *p); |
前置条件:Y 必须是完整类型。当 T 是数组类型时,语句 delete[] p;当 T 非数组类型时,delete p 必须可以执行,不能抛出异常。当 T 是 U[N],Y()[N] 必须可以转化到 T*;当 T 是 U[],Y()[] 必须可以转化到 T*;否则, Y* 必须可以转化到 T*。
效果:当 T 非数组类型时,构造一个拥有指针 p 的shared_ptr。否则,创建一个拥有指针 p 和一个可以调用 delete[] p 的 deleter。
后置条件:use_count() == 1 && get() == p。如果 T 非数组类型并且 p 可以转化到 enable_shared_from_this< V >*,p->shared_from_this()返回 *this 的一个拷贝。
抛出:当资源不能被满足时,std::bad_alloc 或者自定义的异常。
异常安全:如果异常被抛出,T 是数组类型,构造函数调用 delete[] p;T 非数组类型则调用 delete p。
注意:p 必须是指向通过 C++ 的 new 表达式来创建的对象的指针,或者为 0。即使 p 为 0, 后置条件中 use_count() 也为 1;delete 作用于值为 0 的指针无害。
该构造函数是模版函数,是为记忆传进来的指针的真实类型。析构函数将会调用 delete 作用于原始类型的指针,即使当 T 没有一个虚析构函数或者为 void。
带 deleter 的构造函数
1 | template<class Y, class D> shared_ptr(Y * p, D d); |
拷贝构造函数
1 | shared_ptr(shared_ptr const & r); // never throws |
前置条件:Y* 可以转化到 T*。
效果:如果 r 是空的,构造一个空的shared_ptr;否则,构造一个分享 r 的指针所有权的shared_ptr。
后置条件:get() == r.get() && use_count() == r.use_count()。
抛出:无。
Move 构造函数
1 | shared_ptr(shared_ptr && r); // never throws |
前置条件:Y* 可以转化到 T*。
效果:move 构造一个从 r 的 shared_ptr。
后置条件:*this 包含了 r 旧的值。r 为空并且 r.get() == 0。
抛出:无。
aliasing 构造函数
1 | template<class Y> shared_ptr(shared_ptr<Y> const & r, element_type * p); // never throws |
weak_ptr 构造函数
1 | template<class Y> explicit shared_ptr(weak_ptr<Y> const & r); |
前置条件:Y* 可以转化到 T*。
效果:构造一个分享 r 所有权的 shared_ptr 并且复制 r 中的指针。
后置条件:use_count() == r.use_count()。
抛出:当 r.use_count() == 0, bad_weak_ptr。
异常安全:如果异常抛出,构造函数将无效果。
auto_ptr 构造函数
1 | template<class Y> shared_ptr(std::auto_ptr<Y> & r); |
前置条件:Y* 可以转化到 T*。
效果:构造一个 shared_ptr,存储 r.release() 的拷贝
后置条件:use_count() == 1。
抛出:当资源不能被满足时,std::bad_alloc 或者自定义的异常。
异常安全:如果异常抛出,构造函数将无效果。
unique_ptr 构造函数
1 | template<class Y, class D> shared_ptr(std::unique_ptr<Y, D> && r); |
析构函数
1 | ~shared_ptr(); // never throws |
效果:
如果 *this 为空,或者与另外一个 shared_ptr 共享所有权 (use_count()>1),那么没有副作用。
否则,如果 *this 拥有一个指针 p 和一个 deleter d,d(p) 被调用。
否则,*this 拥有一个指针 p, delete p 被调用。
抛出: 无。
赋值
1 | shared_ptr & operator=(shared_ptr const & r); // never throws |
效果:等同于 shared_ptr(r).swap(*this)。
返回:*this。
注意:因为临时对象的构造会使得 use_count() 更新
1 | shared_ptr & operator=(shared_ptr && r); // never throws |
效果:等同于 shared_ptr( std::move(r) ).swap(*this)。
返回:*this。
1 | shared_ptr & operator=(std::nullptr_t); // never throws |
效果:等同于 shared_ptr().swap(*this)。
返回:*this。
reset
1 | void reset(); // never throws |
效果:等同于 shared_ptr().swap(*this)。
1 | template<class Y> void reset(Y * p); |
效果:等同于 shared_ptr(p).swap(*this)。
1 | template<class Y, class D> void reset(Y * p, D d); |
效果:等同于 shared_ptr(p,d).swap(*this)。
1 | template<class Y, class D, class A> void reset(Y * p, D d, A a); |
效果:等同于 shared_ptr(p,d,s).swap(*this)。
1 | template<class Y> void reset(shared_ptr<Y> const & r, element_type * p); // never throws |
效果:等同于 shared_ptr(r,p).swap(*this)。
indirection
get
1 | element_type * get() const; // never throws |
返回:存储的指针。
抛出:无。
unique
1 | bool unique() const; // never throws |
返回:use_count() == 1。
抛出:无。
注意:unique() 比 use_count() 更快。如果使用 unique() 来实现写拷贝,当存储的指针为 0 时不要依赖特定的值。(?)
use_count
1 | long use_count() const; // never throws |
返回:共享所有权的 shared_ptr 的数量,若 *this 为空则为 0。
抛出:无。
注意:use_count() 不是高效的,只是用来调试和测试。
conversions
swap
1 | void swap(shared_ptr & b); // never throws |
效果:交换两个智能指针的内容
抛出:无。
Free Functions
Example
shared_ptr_example.cpp 提供了一个完整的例子,该程序包含了 shared_ptr 对象的 std::vector 和 std::set。
注意容器生成后,shared_ptr 的 use_count() 为 2.
1 |
|
编译运行
1 | g++ -g shared_ptr_example.cpp -o shared_ptr_example |
Handle/Body Idiom
shared_ptr 的一个常用地方是实现一个 handle/body (pimpl) idiom, 它可以避免头文件中类的具体实现。
]shared_ptr_example2_test.cpp 例子包含了一个头文件 shared_ptr_example2.hpp,该头文件利用 shared_ptr 去隐藏了不完整类型的具体实现。 shared_ptr_example2.cpp 则包含了具体实现。注意这里不需要一个 explicit 的析构函数。与 ~scoped_ptr 不同,~shared_ptr 不需要 T 是完整类型。(?)
1 | // Boost shared_ptr_example2 header file -----------------------------------// |
1 | // Boost shared_ptr_example2 implementation file -----------------------------// |
1 | // Boost shared_ptr_example2_test main program ------------------------------// |
编译运行1
2
3
4
5
6
7
8g++ -g shared_ptr_example2.cpp shared_ptr_example2_test.cpp -o shared_ptr_example2_test
./shared_ptr_example2_test
use_count() is 1
use_count() is 2
destroying implementation
use_count() is 3
destroying implementation
这里第一个 destroying implementation 的输出是由于 c = a 语句导致 c 中的 _imp 被析构。
Thread Safety
shared_ptr 对象提供了 C++ 内建类型的线程安全级别。 shared_ptr 对象能被多个线程同时读,不同的 shared_ptr 对象能够被多个线程同时写(即使这些对象是共享同一个引用)。
Example:1
shared_ptr<int> p(new int(42));
1 | //--- Example 1 --- |
1 | //--- Example 2 --- |
1 | //--- Example 3 --- |
1 | //--- Example 4 --- |
1 | //--- Example 5 --- |
从 Boost release 1.33.0 开始,shared_ptr 在大多平台上使用了通用锁的实现。
如果你的程序是单线程的,并且不需要链接使用了 shared_ptr 的库,你可以使用宏定义 #define BOOST_SP_DISABLE_THREADS 来切换到普通非原子的引用计数更新。
你能定义宏 BOOST_SP_USE_PTHREADS 来关闭通用锁,切换回使用普通的 pthread_mutex_t 的代码。