生存期間
すべてのオブジェクトおよび参照は生存期間を持ちます。 これは実行���の性質です。 任意のオブジェクトまたは参照について、プログラムの実行の、その生存期間が始まる時点があり、それが終わる時点があります。
- トリビアルなデフォルトコンストラクタ以外によって初期化される (またはいずれかの部分オブジェクトがそのように初期化される) クラスまたは集成体型のオブジェクトの場合、生存期間は初期化が終わったときに始まります。
- デストラクタがトリビアルでないクラス型のオブジェクトの場合、生存期間はデストラクタの実行が開始するときに終わります。
- 共用体のメンバの生存期間は、そのメンバがアクティブになったときに始まります。
- 他のすべてのオブジェクト (トリビアルなデフォルトコンストラクタによって初期化されるクラスオブジェクト、非クラスオブジェクト、それらの配列、など) の場合、生存期間は、そのオブジェクトのために適切にアラインされた記憶域が確保されたときに始まり、その記憶域が解放されたまたは別のオブジェクトによって再利用されたときに終わります。
オブジェクトの生存期間はその記憶域の生存期間と等しいまたはネストされます (記憶域期間を参照してください)。
参照の生存期間は、その記憶域期間と同一です。 |
(C++14未満) |
参照の生存期間は、その初期化が完了したときに始まり、スカラーオブジェクトであるかのように終わります。 |
(C++14以上) |
ノート: 参照先のオブジェクトの生存期間が、参照の生存期間が終わる前に終わることもあります。 これはダングリング参照を発生させます。
メンバオブジェクトおよび基底部分オブジェクトの生存期間は、クラスの初期化の順序に従って、始まり、終わります。
目次 |
[編集] 一時オブジェクトの生存期間
一時オブジェクトは以下の状況で作成されます。 (C++17未満)一時オブジェクトは prvalue が 具体化されたときに glvalue として使用できるようにするために作成されます。 これは以下の状況で発生します。 (C++17以上)
(C++17未満) | |
|
(C++17以上) |
一時オブジェクトの具体化は、一般的に、不要な一時オブジェクトの作成を避けるために、可能な限り遅延されます。 コピー省略を参照してください。 |
(C++17以上) |
すべての一時オブジェクトは、それが作成された地点を (文脈的に) 含む完全式の評価における最後のステップとして破棄され、複数の一時オブジェクトが作成された場合、それらは作成順序の逆順で破棄されます。 これは、たとえその評価が例外を投げることによって終了しても、真です。
これには2つの例外があります。
- 一時オブジェクトの生存期間は、 const 左辺値参照にまたは右辺値参照に (C++11以上)束縛することによって、延長さ���ることがあります。 詳細については参照初期化を参照してください。
|
(C++11以上) |
[編集] 記憶域の再利用
オブジェクトがトリビアルに破棄可能な場合、または、プログラムがデストラクタの副作用に頼らない場合、生存期間を終了させるためにそのオブジェクトのデストラクタを呼ぶことは要求されません。 しかし、非トリビアルなオブジェクトの生存期間を明示的に終了させた場合は、デストラクタが暗黙に呼ばれる (すなわち、自動オブジェクトの場合はスコープの終了または例外によって、スレッドローカルオブジェクトの場合はスレッドの終了によって、または静的オブジェクトの場合はプログラムの終了によって) よりも前に、同じ型の新しいオブジェクトがその場で構築される (例えば配置 new によって) ことを保証しなければなりません。 さもなければ動作は未定義です。
class T {}; // トリビアル struct B { ~B() {} // 非トリビアル }; void x() { long long n; // 自動、トリビアル new (&n) double(3.14); // 異なる型で再利用、OK } // OK void h() { B b; // 自動、トリビアルに破棄可能でない b.~B(); // 生存期間を終了させます (副作用がないため、必須ではありません) new (&b) T; // 異なる型、デストラクタが呼ばれるまでは OK です } // デストラクタが呼ばれます —— 未定義動作
静的、スレッドローカル、または自動記憶域期間の const 完全オブジェクトによって占められているまたは占められていた記憶域期間を再利用することは、そのようなオブジェクトは読み込み専用メモリに格納されているかもしれないため、未定義動作です。
struct B { B(); // 非トリビアル ~B(); // 非トリビアル }; const B b; // const static void h() { b.~B(); // b の生存期間を終了させます。 new (const_cast<B*>(&b)) const B; // 未定義動作、 const の再利用の試み。 }
別のオブジェクトが占めていたアドレスに新しいオブジェクトが作成される場合、元のオブジェクトのすべてのポインタ、参照、名前は自動的に新しいオブジェクトを参照するようになり、新しいオブジェクトの生存期間が始まれば、それらを新しいオブジェクトを操作するために使用することができますが、これは以下の条件が満たされる場合に限ります。
- 新しいオブジェクトのための記憶域が、元にオブジェクトが占めていた記憶域の位置と、正確にオーバーラップしている。
- 新しいオブジェクトが元のオブジェクトと同じ型である (トップレベルの cv 修飾は無視します)。
- 元のオブジェクトの型が const 修飾されていない。
- 元のオブジェクトがクラス型の場合は、 const 修飾された型または参照型の非静的データメンバを含まない。
- 元のオブジェクトが T 型の最も派生したオブジェクトであり、新しいオブジェクトが T 型の最も派生したオブジェクトである (つまり、それらが基底クラス部分オブジェクトでない)。
struct C { int i; void f(); const C& operator=( const C& ); }; const C& C::operator=( const C& other) { if ( this != &other ) { this->~C(); // *this の生存期間が終わります。 new (this) C(other); // C 型の新しいオブジェクトが作成されます。 f(); // well-defined } return *this; } C c1; C c2; c1 = c2; // well-defined c1.f(); // well-defined、 c1 は C 型の新しいオブジェクトを参照します。
上記の条件を満たさない場合でも、ポインタ最適化バリア std::launder を適用することによって、新しいオブジェクトへの有効なポインタを取得できます。 同様に、クラスメンバまたは配列要素の記憶域にオブジェクトが作成された場合、かつ以下を満たす場合、作成されたオブジェクトは、元のオブジェクトを含んでいるオブジェクトの部分オブジェクト (メンバまたは要素) であるだけです。
そうでなければ (部分オブジェクトが参照メンバまたは const 部分オブジェクトを含んでいる場合など)、元の部分オブジェクトの名前は std::launder なしでは新しいオブジェクトをアクセスするために使用することはできません。 struct X { const int n; }; union U { X x; float f; }; void tong() { U u = { { 1 } }; u.f = 5.f; // OK、「u」の新しい部分オブジェクトを作成します。 X *p = new (&u.x) X {2}; // OK、「u」の新しい部分オブジェクトを作成します。 assert(p->n == 2); // OK assert(*std::launder(&u.x.n) == 2); // OK assert(u.x.n == 2); // 未定義、「u.x」は新しい部分オブジェクトを表しません。 } 特別なケースとして、以下を満たす場合、オブジェクトは
配列のその部分が以前に別のオブジェクトのための記憶域を提供していた場合、そのオブジェクトの生存期間はその記憶域が再利用されたために終了しますが、配列それ自身の生存期間は終了しません (その記憶域は再利用されたとみなされません)。 template<typename ...T> struct AlignedUnion { alignas(T...) unsigned char data[max(sizeof(T)...)]; }; int f() { AlignedUnion<int, char> au; int *p = new (au.data) int; // OK、 au.data は記憶域を提供します。 char *c = new (au.data) char(); // OK、 *p の生存期間は終了します。 char *d = new (au.data + 1) char(); return *c + *d; // OK } |
(C++17以上) |
[編集] 生存期間外のアクセス
オブジェクトの生存期間が開始する前かつオブジェクトが占める記憶域が確保された後、またはオブジェクトの生存期間が終了した後かつオブジェクトが占めていた記憶域が再利用または解放される前、そのオブジェクトを表す glvalue 式の以下の使用は未定義です。
- 左辺値から右辺値への変換 (例えば値をとる関数への関数呼び出し)。
- 非静的データメンバへのアクセスまたは非静的メンバ関数の呼び出し。
- 仮想基底クラス部分オブジェクトへの参照の束縛。
- dynamic_cast または typeid 式。
上記のルールはポインタにも同様に適用されます (仮想基底への参照の束縛は仮想基底へのポインタへの暗黙に変換に置き換えられます)。 ポインタの場合はには追加のルールが2つあります。
- オブジェクトのない記憶域を指すポインタの static_cast は、 void* (cv 修飾されていても構いません) へキャストするときのみ許されます。
- void* (cv 修飾されていても構いません) にキャストされたオブジェクトのない記憶域を指すポインタは、 char、 unsigned char、 std::byte (cv 修飾されていても構いません) へのポインタに static_cast することのみできます。
構築中および破棄中は、他の制限が適用されます。 構築中および破棄中の仮想関数呼び出しを参照してください。
[編集] ノート
非クラスオブジェクト (記憶域期間の終了) とクラスオブジェクト (構築の逆順) の生存期間の終了ルールの違いは、以下の例では影響があります。
struct A { int* p; ~A() { std::cout << *p; } // n は a より長生きするため、 well-defined であり、 123 を表示します。 // もし n が a より長生きしなければ、未定義動作です。 }; void f() { A a; int n = 123; // もし n が a より長生きしなければ、これは最適化により削除されるかもしれません (デッドストア)。 a.p = &n; }
少なくとも1つのメジャーな処理系はこのルールを実装しておらず、代わりに n
への格納を最適化により削除します。 生存期間のルールは core issue 2256 で再検討中です。
[編集] 欠陥報告
以下の動作変更欠陥報告は以前に発行された C++ 標準に遡って適用されました。
DR | 適用先 | 発行時の動作 | 正しい動作 |
---|---|---|---|
CWG 2012 | C++14 | lifetime of references was specified to match storage duration, requiring that extern references are alive before their initializers run |
lifetime begins at initialization |
[編集] 関連項目
生存期間 の C言語リファレンス
|