一、析构函数也会抛出异常
演示案例
class Widget
{
public:
~Widget() {} //假设这个析构函数可能会抛出异常
};
int main()
{
std::vector<Widget> v;
return 0;
}//v在这里自动销毁
- 假设v内有10个Widgets,那么在程序结束时会逐个释放这10个Widget对象
- 但是假设在释放第1个对象时,第1个Widget的析构函数中抛出了异常,并且没有对任何异常进行任何处理,此时程序就会中断
二、通过例子来告知如何处理异常
- 现在建立以下两个类,一个类负责连接数据库,另一个用来管理数据库对象
class DBConnection
{
public:
//该函数返回一个DBConnection对象
static DBConnection create();
//关闭数据库连接(失败会抛出异常)
void close();
};
//用来管理DBConnection对象
class DBConn
{
public:
//确保数据库连接总是会被关闭
~DBConn() { db.close(); }
private:
DBConnection db;
};
int main()
{
//建立一个DBConnection对象并交给DBConn管理,使用DBConn的接口管理DBConnection
DBConn dbc(DBConnection::create());
return 0;
}//程序结束时,DBConn对象被销毁,因此会自动为DBConnection对象调用close
- 如果调用close()调用成功的话那么就没有什么事。如果close()函数调用出错(有异常),那么DBConn析构函数也会传播该异常,导致程序出错
两种普通的解决办法
- 解决办法①:
- 如果close函数抛出异常,就结束程序,可以通过调用abort完成
- 如果程序在析构期间发生一个错误,那么“强迫程序结束”是一个合理的设置
DBConn::~DBConn()
{
try { db.close(); }
catch (...) {
//此处还可以做一个记录,记下对close的调用失败
std::abort();
}
}
- 解决办法②:
- 忽略(吞掉)这个异常
- 一般而言,忽略这个异常是个坏主意,因为忽略这个异常会造成不明确的行为
DBConn::~DBConn()
{
try { db.close(); }
catch (...) {
/*此处还可以做一个记录,记下对close的调用失败,
其他什么都不做
*/
}
}
一个更好的解决办法
- 一个更好的策略是重新设计DBConn接口,DBConn可以追踪所管理的DBConnection是否已经关闭
- 如果已经关闭就不做任何事情
- 如果还没关闭,并且抛出了异常,那么还是要使用到上面的两种解决方案
class DBConn
{
public:
DBConn::~DBConn()
{
if (!closed) {
try { db.close(); }
catch () {
//此处还可以做一个记录,记下对close的调用失败
}
}
}
void close() {
db.close();
closed = true;
}
private:
DBConnection db;
bool closed;
};
三、总结
- 析构函数绝对不要抛出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕获并处理该异常
- 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么类应该提供一个普通函数(而非在析构函数中)执行该操作