(转载请注明原创于潘多拉盒子)
智能指针(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种:
- 单持有者,e.g. std::auto_ptr。单持有者保证每个new出来的对象只有一个持有者,单这种持有关系可以转移,转移之后原持有者不再持有对象。
- 局部持有者,e.g. boost::scoped_ptr。类似单持有者,单持有关系无法转移。这决定了这种持有关系不可能脱离当前的作用域(scope),因此得名。
- 共享持有者,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
这其实是一种混用持有关系的问题。这可以简单的归纳为以下几种情况:
- 直接定义在栈上或data区的变量,不需要delete的。
- new出来的对象(在堆上),但程序员自己delete的。
- 不同类型的智能指针持有的对象。
这几种持有情况互相不能混用,否则会segmentation fault。