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

结论

1. 析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该能够捕捉任何异常,然后吞下他们(不传播)或结束程序。

2. 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而不是在析构函数中)执行该操作。


        C++ 不禁止但不鼓励从析构函数引发异常。考虑:

class Widget {
public:
   ...
   ~Widget() { ... } // 假设这里可能吐出一个异常
};

void doSomething()
{
   std::vector<Widget> v;
   ...
} // v在这里被自动销毁
        当 vector v 被析构时,它有责任析构它包含的所有 Widgets。但假设在那些调用期间,先后有两个Widgets抛出异常,对于 C++ 来说,这太多了。在两个异常同时存在的情况下,程序若不是结束执行就是导致不明确行为。在本例中将导致不明确行为,使用标准库的任何其他容器(如list,set)或TR1的任何容器甚至array,也会出现相同情况。C++ 不喜欢析构函数吐出异常。


        如果你的析构函数需要执行一个可能失败而抛出一个异常的操作,该怎么办呢?假设使用一个class负责数据库连接,为了确保客户不会忘记在 DBconnection对象上调用 close(),一个合理的想法是创建一个用来管理DBConnection资源的类,并在其析构函数中调用close:

class DBConn { // 这个类用来管理DBConnection对象
public:        // objects
  ...
  ~DBConn()     // 确保数据库连接总是会被关闭
   { 
       db.close();}
private:
   DBConnection db;
};
它允许客户像这样编程:

{ // 打开一个区块(block)
   DBConn dbc(DBConnection::create());

       // 建立DBConnection并交给DBConn对象以便管理
   ... // 通过DBConn的接口使用DBConnection对象
}     //在区块结束点,DBConn对象被销毁,因而自动为DBConnection对象调用close
        只要调用 close 成功,一切都美好。但是如果这个调用导致一个异常,DBConn 的析构函数将传播那个异常,也就是允许它离开析构函数。这就产生了问题,因为析构函数抛出了一个烫手的山芋。


有两个主要的方法避免这个麻烦

(1)Terminatethe program:如果 close 抛出异常就终止程序,一般是通过调用 abort。

DBConn::~DBConn()
{
try { db.close(); }
   catch (...) {
        制作运转记录,记下对close的调用失败;
        std::abort();
   }
}
       它有一个好处是:阻止异常从析构函数中传播出去(那会导致不明确的行为)。也就是说,调用 abort 可以预先制“不明确行为”于死地。
(2)Swallowthe exception:吞下因调用close而发生的异常。在此例中将在第一种方法下去掉abort那句语句。

         通常,将异常吞掉是个坏主意,因为它隐瞒了“某些动作失败”的重要信息!然而,有些时候,吞下异常比冒程序过早终止或不明确行为的风险更可取。程序必须能够在遭遇到一个错误并忽略之后还能继续可靠地运行,这才能成为一个可行的选择。

以上方法的问题都在于两者无法对引起 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 的析构函数中仍内含一个“双保险调用”)。如果某个操作可能在失败时抛出异常,而又存在某种需要必须处理该异常,那么这个异常必须来自析构函数以外的某个函数。这是因为析构函数)引发异常是危险的,永远都要冒着程序过早终止或 不明确行为的风险。在本例中,让客户自己调用 close 并不是强加给他们的负担,而是给他们一个处理错误的机会。他们可以忽略它,依靠 DBConn 的析构函数去调用 close。如果真有错误发生,close的确抛出异常而且DBConn吞下该异常或结束程序,客户没有立场抱怨,毕竟他们曾有机会第一手处理问题,而他们选择了放弃。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值