C++的优秀特性6:智能指针

时间:2022-03-18 03:13:36

(转载请注明原创于潘多拉盒子)

  智能指针(Smart Pointer)是C++非常重要的特性。考虑如下一段使用简单指针(Plain Pointer)的代码:

A* a = new A();
B* b = new B();
......
if (condition1)
{
// return之前必须delete所有new出来的对象
delete a;
delete b;
return true;
}
......
if (condition2)
{
// return之前必须delete所有new出来的对象
delete a;
delete b;
return true;
}
.......
if (condition3)
{
// throw 之前必须delete所有new出来的对象
delete a;
delete b;
throw std::exception("bad condition3");
} // 有异常必须catch,在rethrow之前delete所有new出来的对象
try
{
......
}
catch (...) // 三个点“...”用于此处捕捉所有异常
{
delete a;
delete b;
throw; // rethrow
}

  这种方式非常繁琐,也很容易犯错误(error-prone)。有没有一种方法,能管理new出来的对象,并且在return/throw/异常发生的时候自动释放这些对象呢?按照《C++的优秀特性3:构造函数和析构函数》的介绍,有一种借助构造函数和析构函数实现的模版类,可以实现这样的功能。这就是智能指针。比如标准库STL中实现了一种智能指针std::auto_ptr。有了这种智能指针,前面的程序就简单了:

std::auto_ptr<A> a(new A());
std::auto_ptr<B> b(new B());
......
a->foo();  // 像普通指针一样使用
(*b).bar();  // 像普通指针一样使用
if (condition1)
{
// return之前无需delete所有new出来的对象
return true;
}
......
if (condition2)
{
// return之前无需delete所有new出来的对象
return true;
}
.......
if (condition3)
{
// throw 之前无需delete所有new出来的对象
throw std::exception("bad condition3");
} // 有异常不处理的时候也不用catch和rethrow了

  一个智能指针指向一个对象叫做这个指针持有这个对象,也就是指针是这个对象的持有者。持有者负责在适当的时机释放持有的对象。常用的智能指针有3种:

  1. 单持有者,e.g. std::auto_ptr。单持有者保证每个new出来的对象只有一个持有者,单这种持有关系可以转移,转移之后原持有者不再持有对象。
  2. 局部持有者,e.g. boost::scoped_ptr。类似单持有者,单持有关系无法转移。这决定了这种持有关系不可能脱离当前的作用域(scope),因此得名。
  3. 共享持有者,e.g. boost::shared_ptr。一个对象可以被一个或多个这样的共享持有者持有,持有关系可以转移,转移之后原持有者继续持有该对象,直到原持有者被析构。当一个对象的最后一个持有者析构时,持有的对象被析构。

在实际中,由于#1(单持有者)的持有关系可以转移,常常是造成麻烦,而应用场景跟#2(局部持有者)又很接近,因此Google C++规范中禁止使用auto_ptr,而建议使用scoped_ptr。共享持有者是很常用的,基本上适用于大多数的简单指针应用的情况。但是,由于shared_ptr是为共享持有关系设计的,因此为了和scoped_ptr区分,在scoped_ptr适用的场景中,不要适用shared_ptr。

  实际中常见的一种错误是,智能指针持有了不该持有的对象。比如:

std::string name = "ZHANG San";
std::auto_ptr<std::string> nameHolder(&name); // 持有了不是new出来的对象,会导致segmentation fault

这其实是一种混用持有关系的问题。这可以简单的归纳为以下几种情况:

  1. 直接定义在栈上或data区的变量,不需要delete的。
  2. new出来的对象(在堆上),但程序员自己delete的。
  3. 不同类型的智能指针持有的对象。

这几种持有情况互相不能混用,否则会segmentation fault。