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()
4

2 に答える 2

82

コンストラクターの委任は、実際、新しい破棄ロジックを導入する新しい機能です。

オブジェクトの存続期間をもう一度見てみましょう。オブジェクトの存続期間は、コンストラクターが終了したときに始まります。(15.2 / 2を参照してください。標準ではこれを「主要なコンストラクター」と呼んでいます。)あなたの場合、これはコンストラクターX(int)です。2番目の委任コンストラクターX()は、現在は単なるメンバー関数として機能します。スコープが巻き戻されると、完全に構築されたすべてのオブジェクトのデストラクタが呼び出されます。これには、が含まれxます。

これの意味は実際には非常に深いものです。コンストラクターを別のコンストラクターに委任する限り、「複雑な」作業負荷をコンストラクターに入れて、通常の例外伝播を最大限に活用できるようになりました。このような設計により、通常のコンストラクターに多くの作業を加える必要がない場合によく使用されていたさまざまな「init」関数が不要になります。

表示されている動作を定義する特定の言語は次のとおりです。

[C++11: 15.2/2]: [..]同様に、オブジェクトの非委任コンストラクターが実行を完了し、そのオブジェクトの委任コンストラクターが例外を除いて終了した場合、オブジェクトのデストラクタが呼び出されます。[..]

于 2013-01-17T19:45:11.193 に答える
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()");    
    }
};

実際にはこのようなものではなく、型は 1 つしかありませんが、コンストラクターが実行される限り類似しており、X(int)委任コンストラクターの追加のコードが実行され、それがX「基本クラス」をスローする場合 (これは実際には基本クラスではありません)。破壊されます。

于 2013-01-18T00:06:06.080 に答える