• Effective C++_笔记_条款08_别让异常逃离析构函数


    (整理自Effctive C++,转载请注明。整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/

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

       1: class Widget{
       2: public:
       3:     ...
       4:     ~Widget() {...}    //假设这个可能吐出一个异常
       5: };
       6:  
       7: void doSomething()
       8: {
       9:     vector<Widget> v ;  //v在这里被自动销毁
      10:     ...
      11: }

        当vector v被销毁,它有责任销毁其内含的所有Widgets。销毁第一个抛出异常,销毁第二个抛出异常…,异常对C++而言太多了。其实,在两个异常同时存在的情况下,程序不是结束执行就是导致不明确的行为。

        C++不喜欢析构函数抛出异常,但如果你的析构函数必须执行一个动作,而该动作可能在失败时抛出异常,该怎么办?例如你有class负责数据库连接:

       1: class DBConnection{
       2: public:
       3:     ...
       4:     static DBconnection create() ; //这个函数返回DBConnection对象
       5:  
       6:     void close () ;  //关闭联机;失败就抛出异常
       7: };

             

        为了确保客户不忘记在DBConnection对象身上调用close(),一个合理的想法是创建一个用来管理 DBConnection资源的class,并在其析构函数中调用close:

       1: class DBConn{     //这个类用来管理DBConnection对象
       2: public:
       3:     ...
       4:     ~DBConn()     //确保数据库连接总是会被关闭
       5:     {
       6:         db.close() ;
       7:     }
       8: private:
       9:     DBConnection db ;
      10: };

        这样如果用户在某一区块,实例化DBConn对象时,在该区块结束时,会自动调用该对象的析构函数,确保该资源管理对象管理的DBConnection对象close。只要调用close成功,一切美好。但如果该调用导致异常,DBConn析构函数会传播该异常。三个办法可以解决这一问题:

    1 如果close抛出异常就结束程序

        通常通过调用abort完成:

       1: DBConn::~DBConn()
       2: {
       3:     try{ db.close }
       4:     catch(...){
       5:         制作运转记录,记下对close的调用失败;
       6:          std::abort();
       7:     }
       8: }

        如果程序遭遇一个“于析构期间发生错误”后无法继续执行,就强迫结束程序。这种方法可以阻止异常从析构函数传播出去,调用abort可以抢先制“不明确行为”于死地。

    2 吞下因调用close而发生的异常

       1: DBConn::~DBConn()
       2: {
       3:     try{ db.close }
       4:     catch(...){
       5:         制作运转记录,记下对close的调用失败;
       7:     }
       8: }

        一般而言,将异常吞掉是个坏主意,因为它压制了“某些动作失败”的重要信息!然而有时候吞下异常也比负担“草率结束程序”或“不明确行为带来的风险好”。

    3 设计接口使用户有机会处理

        一个更好的策略是重新设计DBConn接口,使其客户有机会对可能出现的问题做出反应。例如DBConn自己可以提供一个close函数,因而赋予客户一个机会得以处理“因该操作而发生的异常”。DBConn也可以追踪其所管理的对象是否已被关闭,并在答案为否的情况下有析构函数关闭。

       1: class DBConn{
       2: public:
       3:     ...
       4:     void close () // 供客户使用的新函数
       5:     {
       6:         db.close() ;
       7:        closed = true ;
       8:     }
       9:     ~DBConn()
      10:     {
      11:         if(!closed){
      12:             try{
      13:                 db.close();   //关闭连接(如果客户不那么做)
      14:             }
      15:             catch(...){
      16:                    制作运转记录,记下对close调用失败;    // 如果关闭动作失败,记录下来并结束程序或吞下异常
      17:              }
      18:         }
      19:         
      20:     }
      21: ptivate:
      22:     DBConnection db ;
      23:     bool closed ;
      24: };

        把调用close的责任从DBConn析构函数转移到客户手上是给他们一个处理错误的机会,否则它们没机会响应。如果他们不认为这个机会有用,可以忽略,以来DBConn的析构函数调用close。如果真有错误发生——如果close的确抛出异常——而且DBConn析构函数结束程序或吞下该异常,客户没有立场抱怨。

        请记住:

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

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

  • 相关阅读:
    临时文档
    栈与堆的区别及其探讨
    经典面试题(一)附答案 算法+数据结构+代码 微软Microsoft、谷歌Google、百度、腾讯
    牛顿迭代法求根
    背包问题---01背包|完全背包(装满背包的方案总数分析及实现)
    背包问题----完全背包(最优方案总数分析及实现)
    背包问题----完全背包(详解|代码实现|背包具体物品的求解)
    60行代码:Javascript 写的俄罗斯方块游戏
    同学们博客链接汇总
    (2015秋) 作业1:学生调研(总分10分)
  • 原文地址:https://www.cnblogs.com/hust-ghtao/p/3789224.html
Copyright © 2020-2023  润新知