条款8:别让异常逃离析构函数
c++并不禁止析构函数抛出异常,但并不鼓励这样做。
考虑如下代码:
class Widget
{
public:
//...
~Widget() {} //假设可能会抛出异常
};
void doSomething()
{
std::vector<Widget> v;
//...
}
分析:
当v析构的时候,必须销毁其中的所有的Widgets对象。假设v含有10个Widget,而在析构第一个元素的时候,抛出一个异常。其他九个还是应该被销毁,因此v又调用它们各个析构函数,但若是第二个Widget析构又出现异常,现在有两个同时作用的异常,程序不是强制结束就是导致不明确。因此很有可能出现内存泄漏!
但如果我们现在有这个需求,析构函数必须要执行某个动作,而该动作又可能会失败,失败需要抛出异常,那怎么办呢?
举个例子:
使用该类负责数据库连接
class DBConnection
{
public:
//...
static DBConnection create();
void close();//关闭连接,失败抛出异常!
};
为了确保客户不忘记在DBConnection 对象身上调用close(),我们又创建一个用来管理DBConnection 资源的class,并在它的析构中调用close。
class DBConn
{
public:
//...
~DBConn()
{
db.close();
}
private:
DBConnection db;
};
于是客户写出了这样的代码!
//创建DBConnection对象,并且交给DBConn对象便于管理!
DBConn dbc(DBConnection::create());
我们发现只要调用close成功,一切安好!但是如果抛出异常,则会导致一系列问题!
两个办法:
1、抛出异常,结束进程
~DBConn()
{
try { db.close(); }
catch (...)
{
//制作记录,记下该失败!
std::abort();
}
}
2、抛出异常,吞下,记录相应异常,让程序继续运行
~DBConn()
{
try { db.close(); }
catch (...)
{
//制作记录,记下该失败!
}
}
但这些办法,都没啥吸引力。问题在于都无法对"导致close抛出异常"的情况主动做出反应。
**一个更好的策略:
重新设计DBConn接口,让客户自己有机会可能对问题作出反应!
例如:DBConn提供public的close函数,赋予客户一个机会自己处理该异常,通过一个bool的标志,在析构判断外部是否主动调用关闭,没有调用,自己进行调用!
不过,如果DBConnection析构函数调用close失败,我们又退回了之前要么吞下要么退出的老路!!!
class DBConn
{
public:
//...
void close()
{
db.close();
closed = true;
}
~DBConn()
{
if (!closed)
{
try { db.close(); } //如果客户没自己关闭,这边析构也会关闭!
catch (...)
{
//制作记录,记下该失败!
}
}
}
private:
DBConnection db;
bool closed; //增加一个标志判断,客户是否自己主动调用关闭!!!
};
总结:
- 析构函数尽可能不要抛出异常。如果析构中调用的函数又很有可能抛出异常,则应该捕捉该异常,然后吞下或者终止程序!
- 如果客户需要对某个函数运行期间的抛出异常做出反应,那么class中应该提供一个普通函数执行该操作!
结尾: 我是航行的小土豆,喜欢我的程序猿朋友们,欢迎点赞+关注哦!希望大家多多支持我哦!有相关不懂问题,可以留言一起探讨哦!
如有引用或转载记得标注哦!