技术开发 频道

C++箴言:防止因异常而离开析构函数


【IT168技术文档】

class Widget {  public:   ...   ~Widget() { ... } // assume this might emit an exception }; void doSomething() {  std::vector<Widget> v;  ... } // v is automatically destroyed here
  当 vector v 被析构时,它有责任销毁它包含的所有 Widgets。假设 v 中有十个 Widgets,在销毁第一个的时候,抛出一个异常。其他 9个 Widgets 仍然必须被销毁(否则他们持有的任何资源将被泄漏),所以 v 应该调用它们的析构函数。但是假设在这个调用期间,第二个 Widgets 的析构函数又抛出一个异常。现在有两个异常同时在活动中,对于 C++ 来说这太多了。在非常巧合的条件下发生这样两个同时活动的异常,程序的执行会终止或者引发未定义行为。在本例中,将引发未定义行为。与此相同,使用任何标准库容器(比如,list,set),任何 TR1中的容器,甚至是一个数组,都可能会引发未定义问题。并非必须是容器或数组才会陷入麻烦。程序夭折或未定义行为是析构函数引发异常的结果,即使没有使用容器或数组也会如此。C++ 不喜欢引发异常的析构函数。这比较容易理解,但是如果你的析构函数需要执行一个可能失败而抛出异常的操作,该怎么办呢?例如,假设你与一个数据库连接类一起工作:
class DBConnection {  public:   ...     static DBConnection create(); // function to return   // DBConnection objects; params   // omitted for simplicity   void close(); // close connection; throw an }; // exception if closing fails
  为了确保客户不会忘记调用 DBconnection 对象的 close,一个合理的主意是为 DBConnection 建立一个资源管理类,在它的析构函数中调用 close。这样的资源管理类将在以后的文章中探讨,但在这里,只要认为这样一个类的析构函数看起来像这样就足够了:
class DBConn { // class to manage DBConnection  public: // objects   ...   ~DBConn() // make sure database connections   { // are always closed    db.close();   }  private:   DBConnection db; };
  它允许客户像这样编程:
{  // open a block  DBConn dbc(DBConnection::create()); // create DBConnection object  // and turn it over to a DBConn  // object to manage  ... // use the DBConnection object  // via the DBConn interface } // at end of block, the DBConn // object is destroyed, thus // automatically calling close on // the DBConnection object
  既然能成功地调用 close 那就好了,但是如果这个调用导致了异常,DBConn 的析构函数将散播那个异常,也就是说,它将离开析构函数。这就产生了问题,因为析构函数抛出了一个烫手的山芋。

  有两个主要的方法避免这个麻烦。DBConn 的析构函数能:

  终止程序 如果 close 抛出异常,调用 abort。
DBConn::~DBConn() {  try { db.close(); }  catch (...) {   make log entry that the call to close failed;   std::abort();  } }
  如果程序在析构过程遭遇到错误后不能继续运行,这就是一个合理的选择。它有一个好处是:如果允许从析构函数散播异常可能会引起未定义行为,这样就能防止它发生。也就是说,调用 abort 就预先防止了未定义行为。

  抑制这个异常 起因于调用 close:
DBConn::~DBConn() {  try { db.close(); }  catch (...) {   make log entry that the call to close failed;  } }
0
相关文章