异常与构造函数和析构函数

C++异常处理

如果抛出异常出现在try语句块中,那么就使用后面catch处理异常。如果这一步没有找到匹配的catch,则会在外层函数继续寻找。最后当找不到匹配的catch时,程序将调用标准库函数termintate终止程序执行。

异常与构造函数
  • 构造函数抛出异常

    class Base {
        public:
            Base ()  {
                throw std::exception();
                cout << "Base()" << endl;
            }
            virtual ~Base () {
                cout << "~Base()" << endl;
            }
    };
    
    int main() {
        try {
             Base obj;
        } catch(...) {
          cout << "catch exception" << endl;
        }
    }
    

    从上面例子输出显示: 如果在构造函数中抛出异常,那么对象会构造失败,意味着对象的生存期从来就没开始过,同时也就不会调用析构函数

  • 子类构造函数抛出异常

    class Base {
        public:
            Base ()  {
                cout << "Base()" << endl;
            }
            virtual ~Base () {
                cout << "~Base()" << endl;
            }
    };
    
    class Derviced : public Base {
      public:
          Derviced() : Base() {
              throw std::exception();
              cout << "Derviced()" << endl;
          }
          virtual ~Derviced () {
              cout << "~Derviced()" << endl;
          }
    };
    
    int main() {
        try {
            Derviced obj;
        } catch(...) {
            cout << "catch exception" << endl;
        } 
    }
    

    从第二个例子输出显示:当子类构造函数抛出异常时,会逆序析构父类。当然子类对象仍然构造失败,也不会调用子类的析构函数。

  • 综上

    • C++中通知对象构造失败的唯一方法那就是在构造函数中抛出异常
    • 构造函数中抛出异常将导致对象的析构函数不被执行
    • 当对象发生部分构造时,已经构造完毕的子对象和基类会逆序地被析构
异常与析构函数
  • 别让异常逃离析构函数
    假设你有个class 负责数据库连接

    class DBConnection {
        public:
            ...
            static DBConnection create();
            void close();
    };
    

    为了确保客户不会忘记调用close,我们写一个类来管理资源。

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

    客户可以写出这样的代码

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

    但是如果在析构函数调用导致异常,则它会传播该异常,这一切都会造成不可预知的行为。

    • 改进1
          DBConn::~DBConn()  {
              try {
                  db.close();
              }catch(...)  {
                  // 制作转运记录,记下调用失败
                  std::abort();
              }
          }
      
    • 改进2
          class DBConn  {
              public:
                  void close()  {
                      db.close();
                      closed = true;
                  }
                  ~DBConn()  {
                      if (!closed)  {
                          try {
                              db.close();
                          } catch (...)  {
                              // 制作转运记录,记下调用失败
                              ...
                          }
                      }
                  }
              private:
                  DBConnection db;
                  bool closed;
          }
      
      这样我们就可以给客户赋予一个机会得以处理"因该操作而发生的异常"。同时又可以防止客户忘掉释放连接的情形,但如果在析构函数里发生异常,我们还是走了"吞掉异常,结束程序"的老路。
  • 综上

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

参考资料
《C++ primer》 5th [美] Stanley B.Lippman,Josee Lajoie,Barbara E. Moo
《Effective C++》[美] Scott Meyers

下一篇:编写异常安全代码一

你可能感兴趣的:(异常与构造函数和析构函数)