3

次のコードを検討してください ( と の値が異なる場合renew) cleanse:

struct T {
    int mem;
    T() { }
    ~T() { mem = 42; }
};

// identity functions, 
// but breaks any connexion between input and output
int &cleanse_ref(int &r) {
    int *volatile pv = &r; // could also use cin/cout here
    return *pv;
}

void foo () {
    T t;
    int &ref = t.mem;
    int &ref2 = cleanse ? cleanse_ref(ref) : ref;
    t.~T();
    if (renew)
        new (&t) T;
    assert(ref2 == 42);
    exit(0);
}

assert合格は保証されていますか?

このスタイルが推奨されないことは理解しています。「これは健全な慣行ではない」などの意見は、ここでは重要ではありません

標準的な引用符からの完全な論理的証明を示す回答が必要です。コンパイラの作者の意見も興味深いかもしれません。

編集: 2 つの質問を 1 つにまとめました。renewパラメータを参照してください(renew == 0これは元の質問です)。

EDIT 2:私の質問は本当に次のとおりだと思います:メンバーオブジェクトとは何ですか?

編集 3: 別のcleanseパラメーターを使用するようになりました!

4

2 に答える 2

3

私は最初にこれらの2つの引用符を持っていましたが、今では実際にint &ref = t.mem;は、の存続期間中に発生する必要があることを指定しているだけだと思いますt。あなたの例では、それはどちらですか。

12.7段落1:

自明ではないデストラクタを持つオブジェクトの場合、デストラクタの実行が終了した後にオブジェクトの非静的メンバーまたは基本クラスを参照すると、未定義の動作が発生します。

そしてパラグラフ3:

オブジェクトの直接の非静的メンバーへのポインターを形成する(またはその値にアクセスする)にはobj、の構築objが開始され、その破棄が完了していない必要があります。そうでない場合、ポインター値の計算(またはメンバー値へのアクセス)が行われます。未定義の動作になります。

ここに、タイプの完全なオブジェクトとタイプTのメンバーサブオブジェクトがありintます。

3.8段落1:

タイプのオブジェクトの存続期間は、次の場合にT始まります。

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

タイプのオブジェクトの存続期間は、次の場合にT終了します。

  • Tが自明でないデストラクタ(12.4)を持つクラス型の場合、デストラクタ呼び出しが開始されます。
  • オブジェクトが占有するストレージは、再利用または解放されます。

ちなみに、3.7.3 p1:

これらの[自動ストレージ期間]エンティティのストレージは、それらが作成されたブロックが終了するまで続きます。

そして3.7.5:

メンバーサブオブジェクト、基本クラスサブオブジェクト、および配列要素の保存期間は、それらの完全なオブジェクト(1.8)の保存期間です。

exitしたがって、この例の前にコンパイラがストレージを「解放」する心配はありません。

3.8p2の非規範的なメモには、「12.6.2はベースとメンバーのサブオブジェクトの存続期間を記述している」と記載されていますが、そこでの言語は初期化とデストラクタについてのみ説明しており、「ストレージ」や「存続期間」については説明していません。些細なタイプのサブオブジェクトの「ライフタイム」の定義には影響しません。

私がこのすべてを正しく解釈している場合、renewfalseの場合、完全なクラスオブジェクトの存続期間は明示的なデストラクタ呼び出しの最後で終了しますが、intサブオブジェクトの存続期間はプログラムの終了まで続きます。

3.8段落5と6は、オブジェクトの存続期間の前後の「割り当てられたストレージ」へのポインターと参照は、限られた方法で使用できると述べており、それらを使用して実行できない可能性のある多くのことをリストしています。ref == 42式が要求するように、左辺値から右辺値への変換はそれらの1つですが、の存続期間がintまだ終了していない場合は問題になりません。

ですから、私はrenew間違っていると思いますが、プログラムは整形式でassert成功しています!

trueのrenew場合、ストレージはプログラムによって「再利用」されるため、元のストレージの有効期間が終了し、int別のストレージの有効期間がint始まります。しかし、次に3.8段落7に入ります。

オブジェクトの存続期間が終了した後、オブジェクトが占有していたストレージが再利用または解放される前に、元のオブジェクトが占有していたストレージの場所に新しいオブジェクトが作成された場合、元のオブジェクトを指すポインター、元のオブジェクトを参照するか、元のオブジェクトの名前が自動的に新しいオブジェクトを参照し、新しいオブジェクトの存続期間が開始されると、次の場合に新しいオブジェクトを操作するために使用できます。

  • 新しいオブジェクトのストレージは、元のオブジェクトが占めていたストレージの場所と正確に重なっています。
  • 新しいオブジェクトは元のオブジェクトと同じタイプであり(最上位のcv-qualifiersを無視します)、
  • 元のオブジェクトの型はconst修飾されておらず、クラス型の場合、型がconst修飾または参照型である非静的データメンバーを含まず、
  • 元のオブジェクトはタイプの最も派生したオブジェクト(1.8)でTあり、新しいオブジェクトはタイプの最も派生したオブジェクトですT(つまり、これらは基本クラスのサブオブジェクトではありません)。

ここでの最初の箇条書きは、最も難しいものです。のような標準レイアウトクラスのT場合、同じメンバーが常に同じストレージに存在する必要があります。タイプが標準レイアウトでない場合、これが技術的に必要かどうかはわかりません。

まだ使用できるかどうかrefはわかりませんが、この例には別の問題があります。

12.6.2パラグラフ8:

クラスのコンストラクターの呼び出しXが完了した後、コンストラクターの本体の複合ステートメントXの実行中にのメンバーが初期化されておらず、値も与えられていない場合、メンバーの値は不確定です。

t.mem実装がゼロまたはに設定されている場合、実装は準拠していることを意味します0xDEADBEEF(また、コンストラクターを呼び出す前に、デバッグモードが実際にそのようなことを行う場合があります)。

于 2012-07-24T19:24:11.990 に答える
0

メモリを破棄していません。手動でデストラクタを呼び出しただけです (このコンテキストでは、通常のメソッドを呼び出すのと同じです)。t変数のメモリ (スタック部分) は「解放」されませんでした。したがって、このアサートは常に現在のコードで渡されます。

于 2012-07-24T19:08:33.083 に答える