EffectiveC++详解:条款08-别让异常逃离析构函数

文章目录

  • 条款08-别让异常逃离析构函数
    • C++ 并不禁止析构函数抛出异常,但不鼓励你这么做。
    • 析构函数的某个动作可能抛出异常,怎么办?
    • 总结

@Author:CSU张扬
@Email:[email protected] or [email protected]
@我的网站: https://www.cppbug.com

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

C++ 并不禁止析构函数抛出异常,但不鼓励你这么做。

下面这段代码,试图在析构函数里抛出异常。

#include 
using namespace std;
class Object {
  public:
   ~Object();
};
Object::~Object() {
  cout << "Deconstructor is invoked." << endl;
  throw invalid_argument("another exception");
}

int main(int argc, char *argv[]) {
    Object obj;
    return 0;
}

我们运行后,gcc会报错:大体意思就是C++11默认析构函数是无异常的。这个程序会在抛出异常后停止。

7.cpp: In destructor 'Object::~Object()':
7.cpp:9:45: warning: throw will always call terminate() [-Wterminate]
   throw invalid_argument("another exception");
                                             ^
7.cpp:9:45: note: in C++11 destructors default to noexcept
Deconstructor is invoked.

This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.
terminate called after throwing an instance of 'std::invalid_argument'
  what():  another exception

析构函数的某个动作可能抛出异常,怎么办?

假设我们有个类负责数据库连接。

class BDConnection {
public:
    static DBConnection create();  // 返回一个DBConnection对象
    ... ...
    void close();  // 关闭连接;失败会抛出异常
};

为了确保用户不会忘记调用 close() 来关闭连接,我们创建一个用来管理 DBConnection 对象的类,在其析构函数里调用 close()

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

客户此时会写出类似下面的这段代码:

{
    DBConn dbc(DBConnection::create());
}

在作用域结束时,会调用 dbc 的析构函数,从而调用 close() 关闭连接。但是如果调用 close() 抛出了异常,就会产生一些麻烦,例如程序终止或者其他的未定义行为。

我们有两个办法来解决这个问题:

  1. close() 抛出异常,那么程序就结束。

    DBConn::~DBConn() {
        try {
            db.close();
        } catch (...) {
            ... ...//记录下 这次异常。
            std::abort();
        }
    }
    
  2. 吞下异常,即:不作处理

    DBConn::~DBConn() {
        try {
            db.close();
        } catch (...) {
            ... ...//记录下 这次异常。
        }
    }
    

这两个办法都不太好,一个较好的策略是重新设计 DBConn,使用户有机会对可能出现的问题作出反应。

class DBConn {
public:
    ... ...
    void close() {
        db.close();
        closed = true;
    }
    ~DBConn() {
        if (!closed) {
            try {
                db.close();  // 如果客户没有手动调用close()
            } catch(...) {
                ... ...// //记录下 这次异常。
            }
        }
    }
private:
    DBConnection db;
    bool closed;
}

如果某个操作可能抛出异常,又必须要处理该异常,那么这个异常必须来自析构函数之外的某个函数。因为析构函数内抛出异常,往往会程序结束或者发生未定义行为。

总结

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

你可能感兴趣的:(EffectiveC++详解)