[Effective C++ --008]别让异常逃离析构函数

时间:2023-03-09 16:22:06
[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应该提供一个普通函数(而不是在析构函数中)执行该操作。