析构函数绝对不要吐出异常
。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序- 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么 class 应该提供一个普通函数(而非在析构函数中)执行该操作
如果在析构函数中必须要执行某个动作,这个动作可能会在失败时抛出异常:
// DBConnection类负责与数据库的连接
class DBConnection {
public:
static DBConnection create();
void close();
//...
};
为了保证在用户在忘记调用 DBConnection 对象的 close() 函数时,数据库的连接也能够正确的关闭,一个合理的想法就是创建一个用来管理 DBConnection 资源的 class DBConn,并在 DBConn 的析构函数中调用 DBConnection 的 close() 函数,大概是这个样子:
//DBConn类负责管理DBConnection对象的连接状态
class DBConn {
public:
...
~DBConn()
{
db.close();
}
private:
DBConnection dbConnection;
};
当使用 DBConn 时,被销毁的时候调用析构函数,但是如果导致异常,DBConn 析构函数会传播该异常,也就是允许它离开这个析构函数。这样会抛出难以驾驭的麻烦。
两个办法可以避免这一问题。DBConn 的析构函数可以:
- 如果 close 抛出异常就结束程序。通常通过调用 abort 完成:
DBConn::~DBConn()
{
try {db.close();}
catch (...) {
制作运转记录,记下对close的调用失败;
std::abort(); //阻止异常从析构函数传播出去
}
}
- 吞下因调用close而发生的异常:
DBConn::~DBConn()
{
try {db.close();}
catch (...) {
制作运转记录,记下对close的调用失败;
}
}
上面两种办法都没什么吸引力。因为它们都无法对“导致 close 抛出异常”的情况做出反应。
一个较佳的选择是重新设计 DBConn 接口,专门提供一个 close 函数,赋予客户一个处理异常的机会,如果客户不愿处理,则再按照之前的方式执行:
class DBConn {
public:
...
void close() //供客户使用的新函数,客户可以通过检查closed来判断关闭过程中是否出现异常,并能够加以处理
{
db.close();
closed = true;
}
~DBConn()
{
if (!closed) {
try {
db.close();
} catch (...) {
// 制作运转记录,记下对close的调用失败
...
}
}
}
private:
DBConnection db;
bool closed;
};
如果某个操作可能在失败时抛出异常,而又存在某种需要必须处理该异常,那么这个异常必须来自析构函数以外的某个函数。因为析构函数吐出异常就是危险的行为。