6

コンストラクターがオブジェクトを削除するスレッドを生成するクラスがあるとします。

class foo {
public:
    foo() 
    : // initialize other data-members
    , t(std::bind(&foo::self_destruct, this)) 
    {}

private:
    // other data-members
    std::thread t;
    // no more data-members declared after this

    void self_destruct() { 
        // do some work, possibly involving other data-members
        delete this; 
    }
};

ここでの問題は、コンストラクターが終了する前にデストラクタが呼び出される可能性があることです。この場合、これは合法ですか?tは最後に宣言 (したがって初期化) され、コンストラクタ本体にはコードがなく、このクラスをサブクラス化するつもりはないため、 が呼び出されたときにオブジェクトが完全に初期化されていると想定しますself_destruct。この仮定は正しいですか?

そのステートメントの後に使用されていないdelete this;場合、ステートメントはメンバー関数で合法であることを私は知っています。thisしかし、コンストラクターはいくつかの点で特別なので、これが機能するかどうかはわかりません。

また、それが違法である場合、それを回避する方法がわかりません。オブジェクトの構築後に呼び出す必要がある特別な初期化関数でスレッドを生成する他の方法もありますが、これは本当に避けたいと思います。

PS: C++03 の回答を探しています (このプロジェクトでは古いコンパイラに制限されています)。例のstd::threadは、説明を目的としたものです。

4

4 に答える 4

7

まず、型のオブジェクトのfooコンストラクターが自明でないため、自明でない初期化があることがわかります (§3.8/1)。

オブジェクトがクラスまたは集約型であり、それまたはそのメンバーの 1 つが自明なデフォルト コンストラクター以外のコンストラクターによって初期化される場合、そのオブジェクトは非自明な初期化を持つと言われます。

これで、コンストラクターが終了した後に type の有効期間のオブジェクトがfoo開始されることがわかります (§3.8/1):

型のオブジェクトの有効期間は、次のT時点で始まります。

  • タイプ T に適した位置合わせとサイズのストレージが取得されます。
  • オブジェクトに重要な初期化がある場合、その初期化は完了しています。

現在、型に自明でないデストラクタがある場合delete、コンストラクタの終了前にオブジェクトに対して行うと、未定義の動作になります (§3.8/5):foo

オブジェクトの有効期間が開始する前であるが、オブジェクトが占有するストレージが割り当てられた後 [...] オブジェクトが配置される、または配置されたストレージの場所を参照するポインターは使用できますが、限られた方法でのみ使用できます。建設中または破壊中のオブジェクトについては、12.7 を参照してください。さもないと、 [...]

オブジェクトは作成中なので、§12.7 を見てみましょう。

仮想関数 (10.3) を含むメンバー関数は、構築または破棄 (12.6.2) 中に呼び出すことができます。

つまりself_destruct、オブジェクトの構築中に を呼び出しても問題ありません。ただし、このセクションでは、オブジェクトの構築中にオブジェクトを破棄することについては特に何も述べていません。そのため、 の操作を確認することをお勧めしますdelete-expression

まず、「削除されるオブジェクト [...] のデストラクタ (存在する場合) を呼び出します」。デストラクタはメンバー関数の特殊なケースなので、呼んでも問題ありません。ただし、§12.4 デストラクタは、構築中にデストラクタが呼び出されたときに明確に定義されているかどうかについては何も述べていません。ここでは運がありません。

第二に、「削除式は割り当て解除関数を呼び出します」および「割り当て解除関数は、ポインターによって参照されるストレージの割り当てを解除します」。繰り返しになりますが、現在構築中のオブジェクトとして使用されているストレージに対してこれを行うことについては何も言われていません。

したがって、標準がそれを非常に正確に定義していないという事実により、これは未定義の動作であると私は主張します。

注意: タイプのオブジェクトの有効期間は、デストラクタの呼び出しが開始されるとfoo 終了します。これは、自明でないデストラクタがあるためです。したがってdelete this;、オブジェクトの構築が終了する前に発生した場合、その有効期間は開始前に終了します。これは火遊びです。

于 2013-01-21T17:58:41.137 に答える
2

私はそれが違法であると明確に定義されているとあえて言います(ただし、一部のコンパイラでは明らかにまだ機能する可能性があります)。

これは、「コンストラクタから例外がスローされたときにデストラクタが呼び出されない」のと同じ状況です。

標準によると、delete 式は、最も派生したオブジェクト (1.8) または new 式(5.3.2) によって作成された配列を破棄します。コンストラクターが終了する前は、オブジェクト最派生オブジェクトではなく、直接の祖先の型のオブジェクトです。

あなたのクラスfooには基本クラスがないため、祖先がないため、型がなく、オブジェクトは呼び出さthisれた時点では実際にはまったくオブジェクトではありません。deleteしかし、たとえ基本クラスがあったとしても、そのオブジェクトは最も派生していないオブジェクトになり (依然として不正にレンダリングされます)、間違ったコンストラクターが呼び出されます。

于 2013-01-21T18:09:44.997 に答える
1

正式には、コンストラクターが正常に終了するまでオブジェクトは存在しません。その理由の一部は、コンストラクターが派生クラスのコンストラクターから呼び出される可能性があることです。その場合、明示的なデストラクタ呼び出しを介して構築されたサブオブジェクトを破棄したくないことは確かですdelete this。完全に構築されていないオブジェクト(の一部)を呼び出すことによってUBを呼び出すことはさらに少なくなります。


オブジェクトの存在に関する標準、強調が追加されました:

C++11§3.8/1:オブジェクト
存続期間は、オブジェクトの実行時プロパティです。オブジェクトがクラスまたは集合体タイプであり、オブジェクトまたはそのメンバーの1つが、単純なデフォルトコンストラクター以外のコンストラクターによって初期化される場合、そのオブジェクトは重要な初期化を持っていると言われます。[注:簡単なコピー/移動コンストラクターによる初期化は、簡単ではない初期化です。—end note ]タイプTのオブジェクトの有効期間は、次の場合に始まります
。—タイプTの適切な配置とサイズのストレージが取得され、
—オブジェクトの初期化が重要な場合、その初期化は完了します。

この場合のコンストラクターは、ユーザーが提供するだけで簡単ではありません。

于 2013-01-21T18:00:17.753 に答える
1

delete this;ほとんどのプラットフォームで実際に正しく動作します。プラットフォーム固有の拡張機能として正しい動作を保証するものもあります。しかし、IIRC は標準に従って明確に定義されていません。

あなたが依存している動作は、メンバー関数が実際にアクセスしない限り、死んだオブジェクトで非仮想非静的メンバー関数を呼び出すことができることが多いということですthis。しかし、この動作は標準では許可されていません。せいぜい移植性がありません。

標準のセクション 3.8p6 では、非静的メンバー関数の呼び出し中にオブジェクトがライブでない場合、未定義の動作になります。

同様に、オブジェクトの有効期間が開始する前であるが、オブジェクトが占有するストレージが割り当てられた後、またはオブジェクトの有効期間が終了した後、オブジェクトが占有していたストレージが再利用または解放される前に、元のオブジェクトは使用できますが、限られた方法でのみ使用できます。建設中または破壊中のオブジェクトについては、12.7 を参照してください。それ以外の場合、そのような glvalue は割り当てられたストレージを参照し、その値に依存しない glvalue のプロパティを使用することは明確に定義されています。次の場合、プログラムは未定義の動作をします。

  • 左辺値から右辺値への変換がそのような glvalue に適用されます。
  • glvalue は、非静的データ メンバーにアクセスするか、オブジェクトの非静的メンバー関数を呼び出すために使用されます。
  • glvalue が基底クラス型への参照に暗黙的に変換される、または
  • glvalue は、変換が最終的にorにstatic_castなる場合を除いて、 a のオペランドとして使用されます。cvchar&cvunsigned char&
  • glvalue は、 a のオペランドdynamic_castまたはのオペランドとして使用されtypeidます。

この特定のケース (作成中のオブジェクトの削除) については、セクション 5.3.5p2 に次のように記載されています。

... 最初の選択肢 ( delete object ) では、オペランドの値はnull ポインター値、前のnew-expressiondeleteによって作成された非配列オブジェクトへのポインター、またはベースを表すサブオブジェクトへのポインターである可能性があります。そのようなオブジェクトのクラス (箇条 10)。そうでない場合、動作は未定義です。2 番目の選択肢 ( delete array ) では、オペランドの値はnull ポインター値または前の配列new-expressionの結果のポインター値である可能性があります。そうでない場合、動作は未定義です。delete

この要件は満たされていません。 new-expressionによって過去形で作成され*thisたオブジェクトではありません。作成中のオブジェクトです(現在進行形)。そして、この解釈は、ポインタが前のnew-expressionの結果でなければならない配列のケースによってサポートされています ... しかし、new-expressionはまだ完全には評価されていません。それは以前のものではなく、まだ結果がありません。

于 2013-01-21T17:53:40.083 に答える