文章目录
- 异常情况
- 总结
异常情况
当我们的析构函数发生异常的时候:C++并不禁止析构函数吐出(emit)异常。
class Widget {
public:...~Widget() { ... } // 假设这可能会引发异常
};
void doSomething()
{std::vector<Widget> v;...
} // v在这里自动销毁
假设v中包含多个Widget对象,销毁第一个时发生异常,销毁第二个时也发挥异常,将产生未定义的行为。
如果析构函数需要执行一个可能会失败并抛出异常的操作,该怎么办?例如,假设你正在使用一个用于数据库连接的类:
class DBConnection {
public:...static DBConnection create(); // 返回DBConnection对象的函数;// 为简单起见,省略了参数void close(); // 关闭连接联系;如果关闭失败,抛出异常
}; class DBConn { // 用来来管理DBConnection对象的类
public: // ...~DBConn() // 确保数据库连接总是关闭的{ db.close();}
private:DBConnection db;
};{ // 开启一个块DBConn dbc(DBConnection::create()); // 创建DBConnection对象// 然后把它交给DBConn对象来管理... // 通过DBConn接口使用DBConnection对象} // block结束时,DBConn对象被销毁,自动在DBConnection对象上调用close
如果调用产生异常,DBConn的析构函数将传播该异常,即允许它离开析构函数。这会导致麻烦,因为析构函数吐出的异常很难处理。
有两种主要方法可以避免这种麻烦。DBConn的析构函数可以:
- 如果发生close抛出异常,就终止程序,通常可以调用abort:
DBConn::~DBConn()
{try { db.close(); }catch (...) {在日志中记录close调用失败;std::abort();}
}
- 吞下调用close引起的异常:
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调用失败; // 记录下来并... // 终止或吞下}}}
private:DBConnection db;bool closed;
};
将调用close的责任从DBConn的析构函数转移到DBConn的客户,(DBConn的析构函数依然包含一个“备用的”调用)。
总结
- 析构函数永远不应该吐出(emit)异常。如果在析构函数中调用的函数可能抛出异常,则析构函数应该捕获任何异常,然后将其吞下或终止程序。
- 如果类客户需要能够对操作期间抛出的异常做出反应,则类应该提供执行该操作的普通(即非析构函数)函数。