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


73

我曾经认为在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()

这是一个显示输出的实时工作区链接
Sam Miller

Answers:


81

委托构造器确实是引入了新的销毁逻辑的新功能。

让我们重温一生的对象:当一个对象的生命周期开始一些构造已经完成。(请参阅15.2 / 2。标准将此称为“主要构造函数”。)在您的情况下,这是构造函数X(int)。第二个委派的构造X()函数现在仅充当普通成员函数。展开作用域后,将调用所有完全构造的对象的析构函数,其中包括x

这实际上意味着非常深远的含义:现在,只要将构造函数委托给另一个构造函数,就可以将“复杂的”工作负载放入构造函数中并充分利用通常的异常传播。这样的设计可以消除对各种“初始化”功能的需求,这些功能通常在不需要将太多工作放到常规构造函数中时很流行。

定义您所看到的行为的特定语言是:

[C++11: 15.2/2]: [..]同样,如果对象的非委托构造函数已完成执行,并且该对象的委托构造函数异常退出,则将调用对象的析构函数。[..]


2
@Nawaz:“基本概念-对象生命周期”?
Kerrek SB 2013年

@KerrekSB:在Lightness_Races_in_Orbit的已删除答案中添加了引号。;-)
Nawaz 2013年

26

我曾经认为在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“基类”(实际上不是基类)被摧毁。


3
奇怪的是,这个答案只能得到一个投票。尽管Kerrek提到了为什么存在此功能,但我认为该功能在回答实际问题方面做得更好。

@Tibo,是的,我也这么认为;-)我参加聚会虽然来晚了,但我认为我写Kerrek的答案已经被接受了。
Jonathan Wakely
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.