这个问题的灵感来自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()
?
答案 0 :(得分:3)
为什么这会对子对象破坏的顺序产生影响?
当在A(int)中捕获时 - 所有子对象都处于活动状态,您可以使用它们。此外,在catch之后,您可以继续对象构造,并“返回”用户正确构造的对象。
当在A()中捕获时 - 所有构造的子对象都被破坏了。并且您不知道哪些子对象是构造的,哪些不是 - 至少使用当前的ISO C ++语法。
为什么A()中抛出的异常会传播到main()?
检查GotW #66:
如果处理程序主体包含语句“throw;”那么catch块显然会重新抛出A :: A()或B :: B()发出的异常。不太明显,但在标准中明确说明,如果catch块没有抛出(重新抛出原始异常,或抛出新的东西),并且控制到达构造函数或析构函数的catch块的末尾,那么原始异常会自动重新抛出。
想想这意味着什么:构造函数或析构函数 - try-block的处理程序代码必须通过发出一些异常来完成。别无他法。语言并不关心它被发出的异常 - 它可以是原始异常,也可以是其他一些翻译异常 - 但必须有例外!基本或成员子对象构造函数抛出的任何异常都不可能导致某些异常泄漏到其包含的构造函数之外。
用更少的词来表示:
如果任何基础或成员子对象的构造失败,则整个对象的构造必须失败。
答案 1 :(得分: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
的实例已完全构建。
析构函数仅在完全构造的对象上运行,即构造函数成功完成的对象,而不会抛出异常。
编辑:根据subobjcets的破坏,第一种情况下的catch
在子对象被破坏后运行。这与成员初始化语法一致,表明它应该实际发生:
A()
try : ob() // default construct
{
throw 4;
}
catch(...)
{
// here ob is already destructed
cout << "Catch in A()\n";
}
(相当于第一种情况。)
答案 2 :(得分:1)
为什么这会对子对象破坏的顺序产生影响?
通常,在A()
的函数catch子句中,您不会知道哪些成员已成功构造,因为异常可能来自其中一个构造函数。因此,为了消除疑虑,他们首先被摧毁。基本上函数try / catch是“在数据成员构造之外”。
为什么A()中抛出的异常会传播到main()?
函数catch子句不能使构造函数成功(因为如果其成员未成功构造,则该对象本身尚未成功构造)。因此,如果您不从中抛出其他内容,则会重新抛出原始异常。这就是它的定义方式,你不能使用function-try子句来忽略这个问题。您可以在函数内部使用常规try / catch来忽略问题,然后由您决定问题是否阻止了对象的正确构造。