C++并不禁止析构函数吐出异常,但它不鼓励你这样做。
class Widget
{
public:
...
~Widget(){...} //假设这个可能吐出一个异常
};
void doSonmething()
{
std::vector<Widget> v;
...
//v在这里自动销毁
}
假设v内含有10个WIdgets,在析构第一个元素时,有个异常抛出。其他九个Widget还是应该被销毁(否则它们保存的任何资源都会发生泄漏),假设调用的析构函数,又抛出一个异常。在两个异常同时存在的情况下,程序若不是结束执行就是导致不明确行为。
假设你使用一个class负责数据库连接:
class DBConnectint{
public:
...
static DBConnection create();
void close(); //关闭联机,失败时抛出异常
};
为保证使用者不忘记表DBConnect对象身上调用close(),一个合理的想法是创建一个用来管理DBConnection资源的class,并在其析构函数中调用close()。
class DBConn //这个class用来管理DBConnection对象
{
public:
...
~DBConn() //确保数据库连接是总会被关闭
{
db.close();
}
private:
DBConnection db;
}
这就允许使用者写下这样的代码:
{
DBConn dbc(DBConnect::create());
}
只要调用close成功,一切都没问题。但是如果该调用导致异常,DBConn析构函数会传播该异常,也就是允许它离开这个析构函数。
两个方法可以避免这一问题。DBConn的析构函数可以:
1.如果close抛出异常就结束程序。通常通过调用abort完成:
DBConn::~DBConn()
{
try {db.close();}
catch(...)
{
制作运转记录,记下对close的调用失败;
std::abort();
}
}
如果一个程序在析构函数期间发生错误后无法执行,强制结束程序可以阻止异常从析构函数中传播出去。
2.吞下因调用close而发生的异常:
DBConn::
DBConn::~DBConn()
{
try{db.close();}
catch(...)
{
制作运转记录,记下对close的调用失败;
}
}
一般而言,将异常吞掉是一个坏主意,因为它”压制了某些动作失败”的重要信息!
这两种办法都不是很完美的做法,因为两者都无法对“导致close抛出异常”的情况作出反应。
我们可以重新设计DBConn接口,使用者有机会对可能出现的问题作出反应。
class DBConn
{
public:
...
void close() //供使用者调用的函数
{
db.close();
closed = true;
}
~DBConn()
{
if (!closed)
{
try()
{
db.close();
}
catch(...) //如果关闭动作失败
{ //记录下来并结束程序
制作运转记录,记下对close的调用失败
//或吞下异常
...
}
}
}
privcate:
DBConnect db;
bool closed;
};
如果某个操作可能在失败时抛出异常,而又存在某种需要必须处理该异常,那么这个异常必须来自析构函数以外的函数。
结论:
析构函数绝对不要吐出异常。如果一个析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序。
如果使用者需要对某个操作函数运行期间抛出的异常作出反应,那么class应该提供一个普通函数(不是析构函数)执行该操作。