1

这个问题的灵感来自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()

4

3 回答 3

3

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

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

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

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

检查GotW #66

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

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

简而言之,它意味着:

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

于 2012-10-25T12:31:57.403 回答
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";
}

(相当于第一种情况。)

于 2012-10-25T12:31:01.577 回答
1

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

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

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

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

于 2012-10-25T12:32:02.123 回答