智能指针是用来存储动态分配给对象的指针的对象,与C++内置的指针十分相似,但是智能指针能够在合适的时间自动删除对象,从而释放空间。智能指针在遇到异常时尤其有用,因为它能够确保动态分配对象的正确析构。同时,智能指针还可以对多个拥有者共享的动态分配对象进行跟踪。
智能指针可以抽象地认为是拥有指向对象的对象,因此当不再需要动态分配的对象时,智能指针会负责释放对象。
智能指针库提供六种智能指针类模板:
scoped_ptr | <boost/scoped_ptr.hpp> | 拥有单个对象, 不能够被复制,即不希望被转让 |
scoped_array | <boost/scoped_array.hpp> | 与scopt_ptr类似,不过是在堆上分配动态数组,弥补标准库中没有指向数组的智能指针缺憾. |
shared_ptr | <boost/shared_ptr.hpp> | Object ownership shared among multiple pointers. |
shared_array | <boost/shared_array.hpp> | Array ownership shared among multiple pointers. |
weak_ptr | <boost/weak_ptr.hpp> | Non-owning observers of an object owned by shared_ptr.与shared_ptr配合使用,象旁观者一样观测资源的使用情况 |
intrusive_ptr | <boost/intrusive_ptr.hpp> | Shared ownership of objects with an embedded reference count. |
这些模板继承并实现了std::auto_ptr模板。
智能指针保证了没有删除而引起的副作用。它的成员方法唯一扔出的异常是std::bad_alloc。
- scoped_ptr
scoped_ptr不能拷贝和赋值是因为scoped_ptr同时把拷贝构造函数和赋值操作符都声明为私有,保证了它管理的指针不能被转让所有权。但是可以使用成员函数swap()可以交换scoped_ptr保存的原始指针。使用scoped_ptr的好处:
1、使用简单,降低错误出现;
2、scoped_ptr实现简单,无冗余操作,安全并保证了效率,可以获得与原始指针同样的速度。
scoped_ptr的用法与auto_ptr的用法几乎一致,两者的相同点:不能作为容器的元素,前者是因为不支持拷贝和赋值,不满足容器对元素类型的要求;后者是因为其转移语义,auto_ptr一旦赋值和拷贝将会失去其原有的功能;两者的不同点:指针的所权:auto_ptr对指针的所有权可以转移,但是同一时刻只能有一个auto_ptr来管理指针。而scoped_ptr完全拒绝指针所有权的转让。
- scoped_array
scoped_array是封装了new[]操作符在堆上分配的动态数组。scoped_array的特点是:
1、构造函数对应的指针对new[]的结果,与scoped_ptr对应的new的结果不同;
2、因为是new[]的结果,对应的是数组,所以该类没有重载单个指针对象的*和->运算符,而提供了[]操作符重载,使得象数组一样用下标访问元素;
3、由于其不是容器,没有提供begin(), end()等类似的迭代器操作函数;
4、scoped_array实现比较精简,只是一个纯粹的数组接口,其功能有限,不能动态增长,也没有迭代器支持,不能与STL算法结合使用;
5、scoped_array不提供数组索引的越界检查,如果使用了越界的索引将会引发未定义行为。
- shared_ptr
shared_ptr作为一个最象指针的智能指针,其功能跟内置指针十分相似,通过引用计算的方法,可以进行任意的赋值和拷贝,可以在任何地方共享,在其引用计数为0时才会删除被包装的动态分配的对象。其作用有:
1、功能全,可以拷贝、赋值,还可以进行比较,即通过get()方法测试两个shared_ptr的相等或不等,以及提供了operator<比较大小;
2、可以与STL标准关联容器(set和map)搭配使用;
3、提供转型的方法:static_pointer_cast<T>(),const_pointer_cast<T>(),dynamic_pointer_cast<T>(), 而不能用static_cast<T*>(),const_cast<T*>(),dynamic_cast<T*>()将shared_ptr对象转换,因为这将会导致转换后的对象不为shared_ptr类型;
4、shared_ptr提供基本的线程安全保证,一个shared_ptr可以被多个线程安全读取;
5、shared_ptr提供了一种传入删除器的构造函数,shared_ptr(Y *p, D d),其第一个参数为要被管理的指针,第二个删除器参数d则告诉shared_ptr在析构时不使用delete来操作指针p,而要用d来操作,即把delete p换成d(p);
6、shared_ptr<void>能够存储void*型的指针,而void*型指针可以指向任意类型,因此shared_ptr<void>就像时一个范型的指针容器,拥有容纳任意类型的能力。将void*类型可以通过static_pointer_cast<T>(),const_pointer_cast<T>(),dynamic_pointer_cast<T>()进行转型,不过代码安全性降低。
- shared_array
shared_array可以认为时shared_ptr和scoped_array的综合体,其注意事项与scoped_array一致。由于shared_ptrshared_ptrshared_ptrshared_ptrshared_ptr可以与STL配合使用,shared_array可以用shared_ptr<std::vector> 或者std:vector<shared_ptr>来代替。scoped_arrayscoped_array
- weak_ptr
weak_ptr作为一个静静的观测者,被设计为与shared_ptr配合使用,获得资源的观测权,但不会共享资源,在构造和析构时不会产生指针引用计数的增加或减少。该智能指针的一个重要用途是获得this指针的shared_ptr,使得对象自己能够生产shared_ptr管理自己:对象使用weak_ptr观测this指针,这并不影响计数,在需要的时候调用lock()函数,返回一个符合要求的shared_ptr供外界使用使用的注意事项有:
1、没有重载*和->操作符,这由于它不能操作资源,不共享指针;
2、其成员函数use_count()可以观测资源的引用计数,并一个expired()的功能等价于use_count()==0,但速度更快,用以判断观测的资源是否还存在;
3、当对象不存在,即expired()==true时,lock()函数将返回一个存储空指针的shared_ptr。
- intrusive_ptr
intrusive_ptr是一个侵入式的引用计数型指针,用于以下两种情形:
1、对内存占用的要求非常严格,要求必须与原始指针一样;
2、现代代码已经有了引用计数机制管理的对象。
注意:boost库不推荐使用intrusive_ptr,如果真的有非常特别的需求而且在shared_ptr性能、开销等方面影响了程序的运行(一般不会出现),才开始考虑intrusive_ptr。
上面说了这么多,咱们来些实际的代码把!
以用途最广泛的shared_ptr为例:
简单的使用例子:
shared_ptr<double[1024]> p1( new double(1024) ); shared_ptr<double[]> p2( new double(n) );
可能内存泄漏的例子:
void 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的命名表明其函数的质量,ok遵守了shared_ptr的使用规则,而bad构造了一个临时的shared_ptr,从而允许了内存泄漏的可能性。因为函数的传参顺序不定,当new int(2)首先传参时,而g()可能传参时扔出异常,导致shared_ptr不会调用其析构函数,更多的信息参考:Herb Sutter's treatment。当然这种异常安全问题可以通过使用在boost/make_shared.hpp
中的make_shared或者allocate_shared工厂函数来解决。
#include <vector>
#include <set>
#include <iostream>
#include <algorithm>
#include <boost/shared_ptr.hpp>
struct Foo
{
Foo( int _x ) : x(_x) {}
~Foo() { std::cout << "Destructing a Foo with x=" << x << "\n"; }
int x;
/* ... */
};
typedef boost::shared_ptr<Foo> FooPtr;
struct FooPtrOps
{
bool operator()( const FooPtr & a, const FooPtr & b )
{ return a->x > b->x; }
void operator()( const FooPtr & a )
{ std::cout << a->x << "\n"; }
};
int main()
{
std::vector<FooPtr> foo_vector;
std::set<FooPtr,FooPtrOps> foo_set; // NOT multiset!
FooPtr foo_ptr( new Foo( 2 ) );
foo_vector.push_back( foo_ptr );
foo_set.insert( foo_ptr );
foo_ptr.reset( new Foo( 1 ) );
foo_vector.push_back( foo_ptr );
foo_set.insert( foo_ptr );
foo_ptr.reset( new Foo( 3 ) );
foo_vector.push_back( foo_ptr );
foo_set.insert( foo_ptr );
foo_ptr.reset ( new Foo( 2 ) );
foo_vector.push_back( foo_ptr );
foo_set.insert( foo_ptr );
std::cout << "foo_vector:\n";
std::for_each( foo_vector.begin(), foo_vector.end(), FooPtrOps() );
std::cout << "\nfoo_set:\n";
std::for_each( foo_set.begin(), foo_set.end(), FooPtrOps() );
std::cout << "\n";
// Expected output:
//
// foo_vector:
// 2
// 1
// 3
// 2
//
// foo_set:
// 3
// 2
// 1
//
// Destructing a Foo with x=2
// Destructing a Foo with x=1
// Destructing a Foo with x=3
// Destructing a Foo with x=2
return 0;
}
线程安全:提供了与内置类型相同的线程安全,智能指针实例可以同时被多线程读,不过仅仅是采用const 操作来获取。而不同的shared_ptr实例即使是在同一个实例的副本时并且共享相同的参考计数时,仍能够被多线程同时采用操作符=或者reset方法写。任何其他的同时访问导致未定义行为。例子:
shared_ptr<int> p(new int(42));
读安全例子:
// thread A
shared_ptr<int> p2(p); // reads p
// thread B
shared_ptr<int> p3(p); // OK, multiple reads are safe
写安全例子:
// thread A
p.reset(new int(1912)); // writes p
// thread B
p2.reset(); // OK, writes p2
写不安全例子:
//--- Example 1 ---
// thread A
p = p3; // reads p3, writes p
// thread B
p3.reset(); // writes p3; undefined, simultaneous read/write
//--- Example 2 ---
// thread A
p3 = p2; // reads p2, writes p3
// thread B
// p2 goes out of scope: undefined, the destructor is considered a "write access"
//--- Example 3 ---
// thread A
p3.reset(new int(1));
// thread B
p3.reset(new int(2)); // undefined, multiple writes
更多关于智能指针如何使用的信息参考: