# Item 8: 防止因为 exceptions(异常)而离开 destructors(析构函数)
作者:Scott Meyers
译者:fatalerror99 (iTePub's Nirvana)
发布:http://blog.csdn.net/fatalerror99/
C++ 并不禁止从 destructors(析构函数)中引发 exceptions(异常),但是它坚决地阻止这样的实践。至于有什么好的理由,考虑:
```
class Widget {
public:
...
~Widget() { ... } // assume this might emit an exception
};
void doSomething()
{
std::vector<Widget> v;
...
} // v is automatically destroyed here
```
当 vector v 被析构时,它有责任析构它包含的所有 Widgets。假设 v 中有十个 Widgets,在第一个的析构过程中,抛出一个 exception(异常)。其它 9 个 Widgets 仍然必须被析构(否则它们持有的所有资源将被泄漏),所以 v 应该调用它们的 destructors(析构函数)。但是假设在这个调用期间,第二个 Widget 的 destructors(析构函数)又抛出一个 exception(异常)。现在同时有两个活动的 exceptions(异常),对于 C++ 来说,这太多了。在非常巧合的条件下产生这样两个同时活动的异常,程序的执行会终止或者引发 undefined behavior(未定义行为)。在本例中,将引发 undefined behavior(未定义行为)。使用任何其它的标准库 container(容器)(比如,list,set),任何 TR1(参见 Item 54)中的 container(容器),甚至是一个 array(数组),都可能会引发同样的 undefined behavior(未定义行为)。也并非必须是 containers(容器)或 arrays(数组)才会陷入麻烦。程序过早终止或 undefined behavior(未定义行为)是 destructors(析构函数)引发 exceptions(异常)的结果,即使没有使用 containers(容器)和 arrays(数组)也会如此。C++ 不喜欢引发 exceptions(异常)的 destructors(析构函数)。
这比较容易理解,但是如果你的 destructor(析构函数)需要执行一个可能失败而抛出一个 exception(异常)的操作,该怎么办呢?例如,假设你与一个数据库连接类一起工作:
```
class DBConnection {
public:
...
static DBConnection create(); // function to return
// DBConnection objects; params
// omitted for simplicity
void close(); // close connection; throw an
}; // exception if closing fails
```
为了确保客户不会忘记在 DBconnection objects(对象)上调用 close,一个合理的主意是为 DBConnection 建立一个 resource-managing class(资源管理类),在它的 destructor(析构函数)中调用 close。这样的 resource-managing classes(资源管理类)将在 Chapter 3(第三章)中一探究竟,但在这里,只要认为这样一个 class(类)的 destructor(析构函数)看起来像这样就足够了:
```
class DBConn { // class to manage DBConnection
public: // objects
...
~DBConn() // make sure database connections
{ // are always closed
db.close();
}
private:
DBConnection db;
};
```
它允许客户像这样编程:
```
{ // open a block
DBConn dbc(DBConnection::create()); // create DBConnection object
// and turn it over to a DBConn
// object to manage
... // use the DBConnection object
// via the DBConn interface
} // at end of block, the DBConn
// object is destroyed, thus
// automatically calling close on
// the DBConnection object
```
只要能成功地调用 close 就可以了,但是如果这个调用导致一个 exception(异常),DBConn 的 destructor(析构函数)将传播那个 exception(异常),也就是说,它将离开 destructor(析构函数)。这就产生了问题,因为 destructor(析构函数)抛出了一个烫手的山芋。
有两个主要的方法避免这个麻烦。DBConn 的 destructor(析构函数)能:
* Terminate the program if close tHRows(如果 close 抛出异常就终止程序),一般是通过调用 abort:
```
DBConn::~DBConn()
{
try { db.close(); }
catch (...) {
make log entry that the call to close failed;
std::abort();
}
}
```
如果在析构的过程遭遇到错误后程序不能继续运行,这就是一个合理的选择。它有一个好处是:如果允许从 destructor(析构函数)传播 exception(异常)可能会导致 undefined behavior(未定义行为),这样就能防止它发生。也就是说,调用 abort 就可以预先防止 undefined behavior(未定义行为)。
* Swallow the exception arising from the call to close(抑制这个对 close 的调用造成的异常):
```
DBConn::~DBConn()
{
try { db.close(); }
catch (...) {
make log entry that the call to close failed;
}
}
```
通常,swallowing exceptions(抑制异常)是一个不好的主意,因为它会隐瞒重要的信息—— something failed(某事失败了)!然而,有些时候,swallowing exceptions(抑制异常)比冒程序过早终止或 undefined behavior(未定义行为)的风险更可取。程序必须能够在遭遇到一个错误并忽略之后还能继续可靠地运行,这才能成为一个可行的选择。
这些方法都不太吸引人。它们的问题首先在于程序无法对引起 close 抛出 exception(异常)的条件做出回应。
一个更好的策略是设计 DBConn 的 interface(接口),以使它的客户有机会对可能会发生的问题做出回应。例如,DBConn 能够自己提供一个 close 函数,从而给客户一个机会去处理从那个操作中发生的 exception(异常)。它还能保持对它的 DBConnection 是否已被 closed 的跟踪,如果没有就在 destructor(析构函数)中自己关闭它。这样可以防止连接被泄漏。如果在 DBConnection(原文如此,严重怀疑此处应为 DBConn ——译者注)的 destructor(析构函数)中对 close 的调用失败,无论如何,我们还可以再返回到终止或者抑制。
```
class DBConn {
public:
...
void close() // new function for
{ // client use
db.close();
closed = true;
}
~DBConn()
{
if (!closed) {
try { // close the connection
db.close(); // if the client didn't
}
catch (...) { // if closing fails,
make log entry that call to close failed; // note that and
... // terminate or swallow
}
}
private:
DBConnection db;
bool closed;
};
```
将调用 close 的责任从 DBConn 的 destructor(析构函数)移交给 DBConn 的客户(同时在 DBConn 的 destructor(析构函数)中包含一个“候补”调用)可能会作为一种肆无忌惮地推卸责任的做法而使你吃惊。你甚至可以把它看作对 Item 18 中关于使 interfaces(接口)易于正确使用的建议的违背。实际上,这都不正确。如果一个操作可能失败而抛出一个 exception(异常),而且可能有必要处理这个 exception(异常),这个 exception(异常)就 has to come from some non-destructor function(必须来自非析构函数)。这是因为 destructor(析构函数)引发 exception(异常)是危险的,永远都要冒着程序过早终止或 undefined behavior(未定义行为)的风险。在本例中,让客户自己调用 close 并不是强加给他们的负担,而是给他们一个时机去应付错误,否则他们将没有机会做出回应。如果他们找不到可用到机会(或许因为他们相信不会有错误真的发生),他们可以忽略它,依靠 DBConn 的 destructor(析构函数)为他们调用 close。如果一个错误恰恰在那时发生——如果由 close 抛出——如果 DBConn 抑制了那个 exception(异常)或者终止了程序,他们将无处诉苦。毕竟,他们无处着手处理问题,他们将不再使用它。
Things to Remember
* destructor(析构函数)应该永不引发 exceptions(异常)。如果 destructor(析构函数)调用了可能抛出异常的函数,destructor(析构函数)应该捕捉所有异常,然后抑制它们或者终止程序。
* 如果 class(类)客户需要能对一个操作抛出的 exceptions(异常)做出回应,则那个 class(类)应该提供一个常规的函数(也就是说,non-destructor(非析构函数))来完成这个操作。
- Preface(前言)
- Introduction(导言)
- Terminology(术语)
- Item 1: 将 C++ 视为 federation of languages(语言联合体)
- Item 2: 用 consts, enums 和 inlines 取代 #defines
- Item 3: 只要可能就用 const
- Item 4: 确保 objects(对象)在使用前被初始化
- Item 5: 了解 C++ 为你偷偷地加上和调用了什么函数
- Item 6: 如果你不想使用 compiler-generated functions(编译器生成函数),就明确拒绝
- Item 7: 在 polymorphic base classes(多态基类)中将 destructors(析构函数)声明为 virtual(虚拟)
- Item 8: 防止因为 exceptions(异常)而离开 destructors(析构函数)
- Item 9: 绝不要在 construction(构造)或 destruction(析构)期间调用 virtual functions(虚拟函数)
- Item 10: 让 assignment operators(赋值运算符)返回一个 reference to *this(引向 *this 的引用)
- Item 11: 在 operator= 中处理 assignment to self(自赋值)
- Item 12: 拷贝一个对象的所有组成部分
- Item 13: 使用对象管理资源
- Item 14: 谨慎考虑资源管理类的拷贝行为
- Item 15: 在资源管理类中准备访问裸资源(raw resources)
- Item 16: 使用相同形式的 new 和 delete
- Item 17: 在一个独立的语句中将 new 出来的对象存入智能指针
- Item 18: 使接口易于正确使用,而难以错误使用
- Item 19: 视类设计为类型设计
- Item 20: 用 pass-by-reference-to-const(传引用给 const)取代 pass-by-value(传值)
- Item 21: 当你必须返回一个对象时不要试图返回一个引用
- Item 22: 将数据成员声明为 private
- Item 23: 用非成员非友元函数取代成员函数
- Item 24: 当类型转换应该用于所有参数时,声明为非成员函数
- Item 25: 考虑支持不抛异常的 swap
- Item 26: 只要有可能就推迟变量定义
- Item 27: 将强制转型减到最少
- Item 28: 避免返回对象内部构件的“句柄”
- Item 29: 争取异常安全(exception-safe)的代码
- Item 30: 理解 inline 化的介入和排除
- Item 31: 最小化文件之间的编译依赖
- Item 32: 确保 public inheritance 模拟 "is-a"
- Item 33: 避免覆盖(hiding)“通过继承得到的名字”
- Item 34: 区分 inheritance of interface(接口继承)和 inheritance of implementation(实现继承)
- Item 35: 考虑可选的 virtual functions(虚拟函数)的替代方法
- Item 36: 绝不要重定义一个 inherited non-virtual function(通过继承得到的非虚拟函数)
- Item 37: 绝不要重定义一个函数的 inherited default parameter value(通过继承得到的缺省参数值)
- Item 38: 通过 composition(复合)模拟 "has-a"(有一个)或 "is-implemented-in-terms-of"(是根据……实现的)
- Item 39: 谨慎使用 private inheritance(私有继承)
- Item 40: 谨慎使用 multiple inheritance(多继承)
- Item 41: 理解 implicit interfaces(隐式接口)和 compile-time polymorphism(编译期多态)
- Item 42: 理解 typename 的两个含义
- Item 43: 了解如何访问 templatized base classes(模板化基类)中的名字
- Item 44: 从 templates(模板)中分离出 parameter-independent(参数无关)的代码
- Item 45: 用 member function templates(成员函数模板) 接受 "all compatible types"(“所有兼容类型”)
- Item 46: 需要 type conversions(类型转换)时在 templates(模板)内定义 non-member functions(非成员函数)
- Item 47: 为类型信息使用 traits classes(特征类)
- Item 48: 感受 template metaprogramming(模板元编程)
- Item 49: 了解 new-handler 的行为
- Item 50: 领会何时替换 new 和 delete 才有意义
- Item 51: 编写 new 和 delete 时要遵守惯例
- Item 52: 如果编写了 placement new,就要编写 placement delete
- 附录 A. 超越 Effective C++
- 附录 B. 第二和第三版之间的 Item 映射