[Effective C++ --008]别让异常逃离析构函数
这章非常容易理解:因为C++并不禁止析构函数吐出异常,只是不鼓励这样做而已。
一、原因
假设我们有10个装着鸡蛋的容器,而且现在我们还想着把它在析构函数打烂。
class Egg { public : ... ~Egg() { // 这里可能出错,导致蛋打不烂 } }; void foo() { vector<Egg> v // 假设v中间有10个Egg .... } // v在这里被自动销毁
如果我们在销毁10个鸡蛋的过程中,在析构第一个鸡蛋的时候,有个异常被抛出,按照销毁机制,后续的9个鸡蛋还是需要被销毁的(否则鸡蛋保存的任何资源都会发生泄漏)。
但是如果后面的鸡蛋仍然抛出异常,在两个异常同时存在的情况下,C++程序会结束执行或者出现不明确的行为。
就算是使用STL的其他容器,还是会发生同样的问题。
为什么呢?因为C++不鼓励析构函数吐出异常。
二、详解
为了方便上面的原因理解,我们可以来尝试一下的例子:
class DB { public : .... static DB create() ;//函数返回DB对象 void close(); //关闭数据库的联机,失败则抛出异常 }
如果为了方便其他人员使用DB类,防止在调用DB的时候忘记关闭连接,那么我们可以贴心一下:
class DBC { public : .... ~DBC() { // 确保每次调析构的时候都会关闭连接 db.close(); } private: DB db; }
其他人直接使用DBC的类就好了,但是如果这样写,就会出现章一种的问题了,如果析构中抛出了异常怎么办?
我们可以用两种方法来解决:
方法1:
DBC::~DBC() { try {(db.close();) } //检查异常 catch (...) { std::abort(); //如果catch到了异常,那么直接强迫结束程序 } }
方法2:
DBC::~DBC() { try {(db.close();) } //检查异常 catch (...) { ... //如果catch到了异常,记录对close调用失败 } }
上面两种方法似乎都会异常进行了"提示",但是都无法针对“导致异常”的情况作出处理。
因此,我们可以考虑重新设计DBC类:
class DBC { public : .... void close() { db.close(); closed = true; } ~DBC() { // 确保每次调析构的时候都会关闭连接 if (!closed) { try {db.close();} catch(...) { ...// 记录异常 } } } private: DB db; bool closed; }
■总结:
1.析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该要能捕捉任何异常,然后“吞下异常”或者终止程序。
2.如果需要对某个操作函数运行期间抛出的异常作出反应,那么class应该提供一个普通函数(而不是在析构函数中)执行该操作。