Skip to content

Latest commit

 

History

History
110 lines (89 loc) · 3.55 KB

File metadata and controls

110 lines (89 loc) · 3.55 KB

条款08:别让异常逃离析构函数

C++并不禁止析构函数抛出异常,但它不鼓励你这样做。考虑以下代码:

class Widget {
public:
  ...
  ~Widget() {...} // 可能抛出异常
};
void doSomething() {
  std::vector<Widget> v;
  ...
} // v被销毁

假设v有十个Widget,而在析构第一个元素期间,有个异常被抛出。假设在调用剩下元素的析构函数又抛出了异常。现在有多个同时作用的异常,程序可能会终止也可能导致未定义行为。C++不喜欢析构函数抛出异常。

如果你的析构函数必须执行一个操作,而该操作可能会在失败时抛出异常,该怎么办?举个例子,假设你使用一个class负责数据库连接:

class DBConnection {
public:
  ...
  static DBConnection create();
  void close(); // 关闭联机,失败则抛出异常
};

为确保客户不忘记在DBConnection对象上调用close(),一个合理的想法是创建一个用来管理DBConnection资源的class,并在其析构函数中调用close。这只需要考虑它们的析构函数就够了:

class DBConn {
public:
  ...
  ~DBConn() {
    db.close();
  }
private:
  DBConnection db;
};

这便允许客户写出这样的代码:

{ // Scope for dbConnection
DBConn dbc(DBConnection::create()); // 建立DBConnection对象
                                    // 并交给DBConn对象以便管理
...                                 // 使用DBConnection对象
}                                   // DBConn对象被销毁,自动调用close

只要close成功,一切都美好。但如果该调用导致异常,DBConn析构函数会允许它离开析构函数。那会造成问题。

有两个办法可以避免这一问题。DBConn的析构函数可以:

  • 如果close抛出异常就结束程序。通常通过调用abort完成:

    DBConn::~DBConn() {
      try { db.close(); }
      catch (...) {
        std::abort();
      }
    }

    如果析构函数抛出异常,强迫结束程序是个合理选项。它可以阻止异常传播出去。

  • 捕获因调用close而发生的异常但不处理

    DBConn::~DBConn() {
      try { db.close(); }
      catch (...) {}
    } 

    一般而言,将异常捕获不处理是个坏主意,因为它压制了某些操作失败的重要信息。为了能让这成为一个可行方案,程序必须能够继续可靠地执行,即使在遇到异常后。

一个更好的策略是重新设计DBConn接口,使客户有机会对可能出现的异常做出反应。

class DBConn {
public:
  void close() {
    db.close();
    closed = true;
  }
  ~DBConn() {
    if (!closed) {
      try { db.close(); }
      catch (...) {
        // 结束程序或吞掉异常
      }
    }
  }
private:
  DBConnection db;
  bool closed;
};

把调用close的责任从DBConn析构函数手上移到DBConn客户手上(但DBConn析构函数内仍有一个双保险调用)。如果某个操作可能在失败时抛出异常,而又存在某种需要必须处理该异常,那么这个异常必须来自析构函数以外的某个函数。由客户自己调用close并不会对他们带来负担,而是给他们一个处理错误的机会。

请记住

  • 析构函数绝对不要抛出异常。如果一个析构函数调用的函数可能抛出异常,析构函数应该捕获任何异常,然后吞下他们或结束程序。
  • 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非析构函数中)执行该操作。