在从构造函数抛出后调用析构函数

我曾经认为在 C + + 中,如果一个构造函数抛出一个异常,这个“部分构造”类的析构函数就不会被调用。

但是在 C + + 11中似乎不再是这样了: 我用 g + + 编译了下面的代码,它将“ X destructor”打印到控制台。为什么会这样?

#include <exception>
#include <iostream>
#include <stdexcept>
using namespace std;


class X
{
public:
X() : X(10)
{
throw runtime_error("Exception thrown in X::X()");
}
X(int a)
{
cout << "X::X(" << a << ")" << endl;
}
~X()
{
cout << "X destructor" << endl;
}
};


int main()
{
try
{
X x;
}
catch(const exception& e)
{
cerr << "*** ERROR: " << e.what() << endl;
}
}

输出

Standard out:
X::X(10)
X destructor
Standard error:
*** ERROR: Exception thrown in X::X()
4563 次浏览

委派构造函数确实是一个新特性,它引入了一种新的销毁逻辑。

让我们回顾一下对象的 一辈子: 当 一些构造函数完成时,对象的生命周期开始。(见15.2。该标准称之为“主构造函数”。)在您的示例中,这是构造函数 X(int)。其次,委托构造函数 X()现在只充当一个普通的成员函数。在范围展开时,调用所有完全构造的对象的析构函数,这包括 x

这实际上意义非常深远: 您现在可以将“复杂的”工作负载放入构造函数中,并充分利用通常的异常传播,只要您将构造函数委托给另一个构造函数即可。这样的设计可以避免对各种“ init”函数的需求,这些函数曾经在不需要在常规构造函数中投入太多工作时很流行。

定义你所看到的行为的具体语言是:

类似地,如果对象的非委托构造函数 如果该对象的委托构造函数已经完成执行,并且该对象的委托构造函数带有异常退出,则将调用该对象的析构函数。< em > [..]

我曾经认为在 C + + 中,如果一个构造函数抛出一个异常,这个“部分构造”类的析构函数就不会被调用。

但是在 C + + 11中似乎不再是这样了

从 C + + 03开始,一切都没有改变(对于一些没有价值的东西; ——)

您所认为的仍然是正确的,但是在抛出异常时 没有部分构造的对象是正确的。

C + + 03 TC1标准说(强调我的) :

部分构造或部分销毁的对象将为其所有完全构造的子对象(即 构造函数已完成执行但析构函数尚未开始执行的子对象。)执行析构函数

也就是说,任何已经完成构造函数的对象都会通过执行析构函数而被销毁,这是一个很好的简单规则。

从根本上说,同样的规则也适用于 C + + 11: 一旦 X(int)返回,对象的“构造函数已经完成执行”,因此它是完全构造的,所以它的析构函数将在适当的时候运行(当它超出作用域或在构造的某个后期阶段抛出异常时)从本质上来说,规则还是一样的。

委托构造函数的主体在另一个构造函数之后运行,可以执行额外的工作,但是这并不改变对象的构造已经完成的事实,因此它是完全构造的。委托构造函数类似于派生类的构造函数,它在基类的构造函数完成后执行更多代码。在某种意义上,你可以这样考虑你的例子:

class X
{
public:
X(int a)
{
cout << "X::X(" << a << ")" << endl;
}
~X()
{
cout << "X destructor" << endl;
}
};
    

class X_delegating : X
{
public:
X_delegating() : X(10)
{
throw runtime_error("Exception thrown in X::X()");
}
};

它不是像这样的 真的,只有一种类型,但是它在运行 X(int)构造函数时是类似的,然后委托构造函数中的额外代码运行,如果抛出 X“基类”(实际上不是基类)就会被销毁。