场景:
如果对象在运行期间出现了异常,C++异常处理模型有责任清除那些由于出现异常所导致的已经失效了的对象(也即对象超出了它原来的作用域),并释放对象原来所分配的资源, 这就是调用这些对象的析构函数来完成释放资源的任务,所以从这个意义上说,析构函数已经变成了异常处理的一部分。
1.析构函数绝对不要抛出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序。
2.如果客户需要对某些函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。
问题描述:
class Widget {
public:
//......
~Widget() { // 假定这个析构函数可能会吐出异常
//......
}
//......
};
void doSomething()
{
//......
std::vector<Widget> v;
//......
}
当vector v被销毁,它有责任销毁其内含的所有Widgets。假设v内含十个Widget,而在析构第一个元素期间,有个异常被抛出。其他九个Widget还是应该被销毁(否则它们保存的任何资源都会发生泄露),因此v应该调用它们各个析构函数。但假设在那些调用期间,第二个Widget析构函数又抛出异常。现在有两个同时作用的异常,这对C++而言太多了。在两个异常同时存在的情况下,程序若不是结束执行就是导致不明确行为。
原因分析:
如果对象发生异常,为避免资源泄漏,需调用析构函数,释放该资源;如果析构过程中再出现异常,那么新出现的异常又该有谁来处理;这就陷入了一个矛盾之中,或者说无限的递归嵌套之中
解决方案:
1、如果close抛出异常就结束程序。通常通过调用abort完成
DBConn::~DBConn() //确保数据库连接总是会被关闭
{
try {
db.close();
}
catch (...) {
//制作运作记录,记下对close的调用失败
std::abort();//如果close抛出异常就结束程序
}
}
2、吞掉异常
DBConn::~DBConn() //确保数据库连接总是会被关闭
{
try {
db.close();
}
catch (...) {
//制作运作记录,记下对close的调用失败
}
}
3、由用户负责资源的释放
class DBConn {
public:
void close() {//供客户使用的新函数
db.close();
closed = true;
}
~DBConn() {//确保数据库连接总是会被关闭
if (!closed) {
try {//关闭连接(如果提供给客户使用的close函数没有调用的话)
db.close();
}
catch (...) {
//制作运作记录,记下对close的调用失败
//结束程序或吞下异常
//std::abort();//如果close抛出异常就结束程序
}
}
}
private:
DBConnection db;
bool closed;
};