【问题标题】:Order of subobject destruction when an exception is thrown from the constructor构造函数抛出异常时子对象的销毁顺序
【发布时间】:2012-10-25 20:45:12
【问题描述】:

这个问题的灵感来自Using an object after it's destructor is called

让我们考虑下面的代码

class B 
  {
  public:
    B() { cout << "Constructor B() " << endl; }
    ~B() { cout << "Destructor ~B() " << endl; }
  };

class A {
public:
  B ob;
  A()
      try
      { 
      throw 4;
      }
    catch(...)
      {
      cout << "Catch in A()\n";
      }

  A(int)
    {
    try
      {
      throw 4;
      }
    catch(...)
      {
      cout << "Catch in A(int)\n";
      }
    }
  };

int main()
  {
  try
  {
      A f;
  }
  catch (...)
  {
      cout << "Catch in main()\n\n";
  }
  A g(1);
  }

它的输出是

Constructor B() 
Destructor ~B() 
Catch in A()
Catch in main()

Constructor B() 
Catch in A(int)
Destructor ~B() 

A(int) 相比,构造函数A() 具有初始化列表try/catch 语法。为什么这对子对象销毁的顺序有影响?为什么A() 中抛出的异常会传播到main()

【问题讨论】:

    标签: c++ exception-handling


    【解决方案1】:

    为什么这会影响子对象的销毁顺序?

    当在 A(int) 中捕获时 - 所有子对象都处于活动状态,您可以使用它们。此外,在 catch 之后,您可以继续构建对象,并“返回”给用户正确构建的对象。

    当在 A() 中捕获时 - 所有已构建的子对象都被破坏。而且你不知道哪些子对象是被构造的,哪些不是——至少在当前的 ISO C++ 语法中是这样。

    为什么 A() 中抛出的异常会传播到 main()?

    查看GotW #66:

    如果处理程序主体包含语句“throw;”那么 catch 块显然会重新抛出 A::A() 或 B::B() 发出的任何异常。不太明显但在标准中明确说明的是,如果 catch 块没有抛出(重新抛出原始异常,或者抛出新的异常),并且控制到达构造函数或析构函数的 catch 块的末尾,那么原始异常会自动重新抛出。

    想想这意味着什么:构造函数或析构函数-try-block 的处理程序代码必须通过发出一些异常来结束。没有别的办法。语言并不关心发出的是什么异常——它可以是原始异常,也可以是其他翻译的异常——但必须有异常!不可能防止基类或成员子对象构造函数引发的任何异常导致某些异常泄漏到其包含的构造函数之外。

    简而言之,就是:

    如果任何基础或成员子对象的构造失败,则整个对象的构造必定失败。

    【讨论】:

      【解决方案2】:

      不同的是在末尾:

      A()
      try
      { 
        throw 4;
      }
      catch(...)
      {
        cout << "Catch in A()\n";
      }
      

      异常被隐式地重新抛出并且没有对象A被构造,而在:

      A(int) {
      try
      { 
        throw 4;
      }
      catch(...)
      {
        cout << "Catch in A(int)\n";
      }
      }
      

      您吞下异常,A 的实例已完全构造。

      析构函数只在完全构造的对象上运行,即构造函数成功完成的对象,不会引发异常。

      编辑:根据子对象的破坏,第一种情况下的catch 在子对象被破坏之后运行。这与暗示它应该实际发生的成员初始化语法一致:

      A()
      try : ob() // default construct
      { 
        throw 4;
      }
      catch(...)
      {
        // here ob is already destructed
        cout << "Catch in A()\n";
      }
      

      (相当于第一种情况。)

      【讨论】:

        【解决方案3】:

        为什么这会影响子对象的销毁顺序?

        一般来说,在A() 的函数catch 子句中,您不会知道哪些成员已成功构造,因为异常可能来自它们的构造函数之一。因此,为了消除怀疑,它们首先被摧毁。基本上,函数 try/catch 是在数据成员构造“之外”。

        为什么 A() 中抛出的异常会传播到 main()?

        函数catch子句不能让构造函数成功(因为如果它的成员没有构造成功,那么对象本身也没有构造成功)。因此,如果您不从中抛出其他东西,那么将重新抛出原始异常。这就是它的定义方式,您不能使用函数尝试子句来忽略问题。您可以在函数中使用常规的 try/catch 来忽略问题,然后由您决定问题是否妨碍了正确构造对象。

        【讨论】:

          猜你喜欢
          • 2018-06-25
          • 1970-01-01
          • 1970-01-01
          • 2017-12-07
          • 2017-12-04
          • 2011-11-04
          • 1970-01-01
          • 2021-10-31
          • 2011-08-04
          相关资源
          最近更新 更多