【问题标题】:Destructor called after throwing from a constructor从构造函数抛出后调用析构函数
【发布时间】:2013-01-01 10:48:12
【问题描述】:

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

【问题讨论】:

标签: c++ exception c++11


【解决方案1】:

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

让我们重温对象的生命周期:对象的生命周期从some 构造函数完成时开始。 (参见 15.2/2。标准将此称为“主构造函数”。)在您的情况下,这是构造函数 X(int)。第二个,委托构造函数X() 现在只是一个普通的成员函数。在范围展开时,将调用所有完全构造对象的析构函数,其中包括 x

这实际上是非常深刻的:您现在可以将“复杂”的工作负载放入构造函数中,并充分利用通常的异常传播,只要您将构造函数委托给另一个构造函数。这样的设计可以消除对各种“init”函数的需求,这些函数过去很流行,只要不希望在常规构造函数中投入太多工作。

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

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

【讨论】:

  • @Nawaz:“基本概念 - 对象生命周期”?
  • @KerrekSB:添加了 Lightness_Races_in_Orbit 已删除答案的引用。 ;-)
【解决方案2】:

我曾经认为在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) 构造函数运行,然后委托构造函数中的附加代码运行,如果这会抛出 @ 987654324@“基类”(实际上不是基类)被破坏。

【讨论】:

  • 奇怪这个答案只得到一个赞成票。尽管 Kerrek 提到了为什么存在这个功能,但我认为这个功能在回答实际问题方面做得更好。
  • @Tibo,是的,我也这么认为 ;-) 虽然我迟到了,但我认为当我写我的答案时,Kerrek 的答案已经被接受了。
猜你喜欢
  • 2012-04-15
  • 2013-07-13
  • 2021-07-19
  • 2011-04-16
  • 2017-02-08
  • 1970-01-01
  • 1970-01-01
  • 2014-09-18
  • 1970-01-01
相关资源
最近更新 更多