最好不要让析构函数吐出异常或进行异常检查。
一、原因
例:
class Widget{
public:
~Widget(){}//假设吐出一个异常
};
void doSomething()
{
std::vector<Widget> v;
}//v在这里被自动销毁
当vector v被销毁,它有责任销毁其内含的所有Widget。假设v内含十个Widget,而在析构第一个元素期间,有个异常被抛出。其他九个Widget还是应该被销毁(否则它们保存的任何资源都会发生泄漏),因此v应该调用它们各个析构函数。但假设在那些调用期间,第二个Widget析构函数又抛出异常。现在有两个同时作用的异常。在两个异常同时存在的情况下,程序若不是结束执行就是导致不明确行为。
二、解决方法
当你的析构函数必须执行一个动作,而该动作可能会在失败时抛出异常,那又该怎么办?
例:
class DBConnection{
public:
static DBConnection create();//这个类型返回DBConnection对象
void close();//关闭联机,失败则抛出异常
};
class DBConn{
public:
~DBConn()
{
db.close();
}
private:
DBConnection db;
};
当close调用导致异常,DBConn析构函数会传播该异常,也就是允许它离开这个析构函数。那会造成问题,因为那就是抛出了难以驾驭的麻烦。
(一)若close抛出异常就结束程序
有两个方法可以避免这一问题。DBConn的析构函数可以:
1.若close抛出异常就结束程序。通常通过调用abort完成:
DBConn::~DBConn()
{
try{db.close();}
catch(){
//制作运转记录,记下对close的调用失败
std::abort();//强迫结束程序
}
}
(二)吞下因调用close而发生的异常
2.吞下因调用close而发生的异常:
DBConn::~DBConn()
{
try{db.close();}
catch(){
//制作运转记录,记下对close的调用失败
}
}
(三)重新设计接口
3.重新设计接口,将这个异常抛出方放在析构函数以外的某个函数:
class DBConn{
public:
void close()
{
db.close();/供客户使用的新函数
closed=true;
}
~DBConn()
{
if(!closed){
try{
db.close();
}
catch(){//若关闭动作失败,记录下来并结束程序或吞下异常
//制作运转记录,记下对close的调用失败
}
}
}
private:
DBConnection db;
bool closed;
};
若某个操作可能在失败时抛出异常,而又存在某种需要必须处理该异常,那么这个异常必须来自析构函数以外的某个函数。
三、总结
1.析构函数绝对不要吐出异常。若一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序。
2.若客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。