智能指针是行为类似于指针的类对象,但这种对象还有其他功能。
void remodel(std::string & str)
{
std::string * ps = new std::string(str);
...
if (weird_thing())
throw exception();
str = *ps;
delete ps;
return;
}
当出现异常时,delete将不被执行,将导致内存泄漏。
当函数终止时(不管是正常终止,还是异常终止),本地变量都将从栈内存中删除——因此指针ps占据的内存将被释放。但是ps指向的内存没有被释放。如果ps有一个析构函数,该析构函数将在ps过期时释放它指向的内存。ps的问题在于它只是一个常规指针,不是有析构函数的类对象。这正是auto_ptr, unique_ptr和shared_ptr背后的思想。模板auto_ptr是C++98提供的解决方案,c++11已将其摒弃,并提供了另外两种解决方案。
使用智能指针
要创建智能指针对象,必须包含头文件memory,然后使用通常的模板语法来实例化所需类型的指针。
template<class X>
class auto_ptr {
public:
explicit auto_ptr(X * p = 0) throw();
...
}
创建智能指针对象
auto_ptr<double> pd(new double);
unique_ptr<double> pdu(new double);
shared_ptr<string> pss(new string);
所有智能指针类都有一个explicit构造函数,该构造函数将指针作为参数,禁止自动将指针转换为智能指针对象。
shared_ptr<double> pd;
double *p_reg = new double;
pd = p_reg;//不可行的
pd = shared_ptr<double>(p_reg);
shared_ptr<double> pshared = p_reg;//不可行的
shared_ptr<double> pshared(p_reg);
智能指针对象的很多方面都类似于常规指针。例如可以对它执行解除引用操作(* ps)、用它来访问结构成员(ps->name)、将它赋给指向相同类型的常规指针。还可以将智能指针对象赋给另一个同类型的智能指针对象。
有关智能指针的注意事项
auto_ptr<string> ps (new string("I reigned lonely as a cloud."));
auto_ptr<string> vocation;
vocation = ps;
如果ps和vocation是常规指针,则两个指针指向同一个string对象。这是不能接受的,因为程序将试图删除同一个对象两次——一次是ps过期时,另一次是vocation过期时。要避免这种问题,方法有以下几种:
• 定义赋值运算符,使之执行深复制,这样两个指针指向不同的对象,其中的一个对象是另一个对象的副本;
• 建立所有权概念,对于特定的对象,只能有一个智能指针可拥有它,这样只有拥有对象的智能指针的构造函数会删除对象。然后,让赋值操作转让所有权,这就是用于auto_ptr和unique_ptr的策略,但unique_ptr的策略更严格;
• 创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数。例如,赋值时,计数将加1,而指针过期时,计数将减1。仅当最后一个指针过期时,才调用delete。这是shared_ptr采用的策略。
int main()
{
using namespace std;
auto_ptr<string> films[5] =
{
auto_ptr<string> (new string("Fowl Balls")),
auto_ptr<string> (new string("Duck Walks")),
auto_ptr<string> (new string("Chicken Runs")),
auto_ptr<string> (new string("Turkey Errors")),
auto_ptr<string> (new string("Goose Eggs")),
};
auto_ptr<string> pwin;
pwin = films[2];
for (int i = 0; i < 5; i++)
{
cout << *films[i] << endl;
}
return 0;
}
上述代码在执行i = 2的for循环时将会出错,这是因为代码pwin = films[2];将所有权转让给了pwin,films[2]自身将是一个空指针。如果上述程序使用shared_ptr代替auto_ptr,则程序将正常运行。如果使用unique_ptr,程序不会等到运行阶段崩溃,而在编译阶段报错。因此unique_ptr比auto_ptr更安全(编译阶段错误比潜在的程序崩溃更安全)
有一种情况下,将一个智能指针赋给另一个并不会留下危险的悬崖指针。
unique_ptr<string> demo(const char * s)
{
unique_ptr<string> temp(new string(s));
return temp;
}
unique_ptr<string> ps;
ps = demo("Uniquely special");
demo()返回一个临时的unique_ptr,然后ps接管了原本归返回的unique_ptr所有的对象。总之,程序试图将一个unique_ptr赋给另一个时,如果源unique_ptr是个临时右值,编译器允许这样做,如果源unique_ptr存在一段时间,编译器将禁止这样做。
相比于auto_ptr,unique_ptr还有一个优点,它有一个可用于数组的变体。
std::unique_ptr<double []>pda(new double(5));
使用new分配内存时,才能使用auto_ptr和shared_ptr,使用new[]分配内存时,不能使用它们。不使用new分配内存时,不能使用auto_ptr或shared_ptr;不使用new或new[]分配内存时,不能使用unique_ptr。
选择智能指针
如果程序要使用多个指向同一个对象的指针,应选择shared_ptr,这样的情况包括:
1.有一个指针数组,并使用一些辅助指针来标识特定的元素,如最大的元素和最小的元素;
2.两个对象包含都指向第三个对象的指针;
3.STL容器包含的指针;
很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr和auto_ptr。如果您的编译器没有提供shared_ptr,可使用Boost库提供的shared_ptr。
如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr。如果函数使用 new分配内存,并返回指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。
在unique_ptr为右值时,可将其赋给shared_ptr。模板shared_ptr包含一个显式构造函数,可用于将右值unique_ptr转换为shared_ptr。
//make_int返回一个unique_ptr<int>
unique_ptr<int> pup(make_int(rand() % 1000));//ok
shared_ptr<int> spp(pup);//not allowed
shared_ptr<int> spr(make_int(rand() % 1000));//ok