生存期間
全てのオブジェクトと参照は生存期間を持ち、これは実行時のプロパティである:どのオブジェクトや参照にも、プログラムの実行中にその生存期間が始まる点と、終わる瞬間が存在する。
オブジェクトの生存期間は、以下の時に始まる。
- その型に対して適切なアライメントとサイズのストレージが確保され、かつ
- (コンストラクタなし、あるいは自明なデフォルトコンストラクタによるデフォルト初期化を含む)初期化(もしあれば)が完了した場合。ただし、以下の場合は例外である:
- オブジェクトが共用体のメンバまたはそのサブオブジェクトである場合、その共用体メンバが共用体内で初期化されたメンバであるか、アクティブにされた場合にのみ生存期間が始まる。
- オブジェクトが共用体オブジェクト内にネストされている場合、その包含する共用体オブジェクトが自明な特殊メンバ関数によって代入または構築された場合、その生存期間が始まることがある。
- 配列オブジェクトの生存期間は、
std::allocator::allocateによって割り当てられた場合にも始まることがある。
一部の操作は、与えられたストレージ領域に暗黙の生存期間を持つ型のオブジェクトを暗黙的に作成し、その生存期間を開始する。暗黙的に作成されたオブジェクトのサブオブジェクトが暗黙の生存期間を持つ型でない場合、その生存期間は暗黙的に始まらない。
オブジェクトの生存期間は、以下の時に終了する。
- 非クラス型の場合、オブジェクトが破棄される(おそらく擬似デストラクタ呼び出しによって)。または
- クラス型の場合、デストラクタ呼び出しが始まる。または
- オブジェクトが占有していたストレージが解放されるか、オブジェクト自身にネストされていない別のオブジェクトによって再利用される。
オブジェクトの生存期間は、そのストレージの生存期間と等しいか、その中にネストされる。詳細はストレージ期間を参照。
参照の生存期間は、初期化が完了したときに始まり、スカラーオブジェクトであるかのように終了する。
注:参照先のオブジェクトの生存期間は、参照の生存期間が終了する前に終了することがあり、これがダングリング参照を可能にする。
非静的データメンバと基底サブオブジェクトの生存期間は、クラス初期化順序に従って開始および終了する。
目次 |
[編集] 一時オブジェクトの生存期間
一時オブジェクトは、prvalueがglvalueとして使用できるように実体化されるときに作成され、これは(C++17以降)以下の状況で発生する。
|
(C++11以降) |
|
(C++17まで) | ||
一時オブジェクトの実体化は、不必要な一時オブジェクトの作成を避けるため、通常は可能な限り遅延される:コピーエリジョンを参照。 |
(C++17以降) |
|
型
この自由度は、オブジェクトをレジスタで関数に渡したり、関数から返したりすることを可能にするために与えられる。 |
(C++17以降) |
すべての一時オブジェクトは、それらが作成された時点を(字句的に)含む完全式の評価の最後のステップとして破棄され、複数の一時オブジェクトが作成された場合、それらは作成順序とは逆の順序で破棄される。このことは、その評価が例外をスローして終了する場合でも当てはまる。
ただし、以下の例外がある。
- 一時オブジェクトの生存期間は、参照にバインドすることによって延長される場合がある。詳細は参照初期化を参照。
- 配列の要素を初期化またはコピーするために使用されるデフォルトまたはコピーコンストラクタのデフォルト引数を評価するときに作成された一時オブジェクトの生存期間は、配列の次の要素の初期化が始まる前に終了する。
|
(C++17以降) |
|
(C++23から) |
[編集] ストレージの再利用
オブジェクトが自明にデストラクト可能な場合、そのオブジェクトの生存期間を終了させるためにデストラクタを呼び出す必要はない(ただし、プログラムの正しい動作がデストラクタに依存している場合があるので注意すること)。しかし、プログラムが明示的に変数の自明でないデストラクト可能なオブジェクトの生存期間を終了させる場合、デストラクタが暗黙的に呼び出される前、つまり自動オブジェクトの場合のスコープ終了または例外による場合、スレッドローカルオブジェクトの場合のスレッド終了による場合、(C++11以降)または静的オブジェクトの場合のプログラム終了による場合、同じ型の新しいオブジェクトをその場で構築する(例えば、プレイスメントnewによって)ことを保証しなければならない。そうしないと、動作は未定義となる。
class T {}; // trivial struct B { ~B() {} // non-trivial }; void x() { long long n; // automatic, trivial new (&n) double(3.14); // reuse with a different type okay } // okay void h() { B b; // automatic non-trivially destructible b.~B(); // end lifetime (not required, since no side-effects) new (&b) T; // wrong type: okay until the destructor is called } // destructor is called: undefined behavior
静的、スレッドローカル、(C++11以降)または自動ストレージ期間を持つconst完全オブジェクトが占有している、またはかつて占有していたストレージを再利用することは未定義の動作である。なぜなら、そのようなオブジェクトは読み取り専用メモリに格納される可能性があるからである。
struct B { B(); // non-trivial ~B(); // non-trivial }; const B b; // const static void h() { b.~B(); // end the lifetime of b new (const_cast<B*>(&b)) const B; // undefined behavior: attempted reuse of a const }
new式を評価する際、ストレージは割り当て関数から返された後、しかしnew式の初期化子の評価前に再利用されたとみなされる。
struct S { int m; }; void f() { S x{1}; new(&x) S(x.m); // undefined behavior: the storage is reused }
別のオブジェクトが占有していたアドレスに新しいオブジェクトが作成された場合、すべてのポインタ、参照、および元のオブジェクトの名前は自動的に新しいオブジェクトを参照するようになり、新しいオブジェクトの生存期間が始まると、新しいオブジェクトを操作するために使用できる。ただし、元のオブジェクトが新しいオブジェクトによって透過的に置き換え可能である場合に限る。
以下の条件をすべて満たす場合、オブジェクトxはオブジェクトyによって透過的に置き換え可能である。
- yのストレージは、xが占有していたストレージ位置と完全に重なる。
- yはxと同じ型である(トップレベルのcv-修飾子は無視される)。
- xは完全なconstオブジェクトではない。
- xもyも基底クラスサブオブジェクトではない、または
[[no_unique_address]]で宣言されたメンバサブオブジェクトではない(C++20以降)。 - 以下の条件のいずれかが満たされる。
- xとyは両方とも完全なオブジェクトである。
- xとyはそれぞれオブジェクトoxとoyの直接サブオブジェクトであり、かつoxはoyによって透過的に置き換え可能である。
struct C { int i; void f(); const C& operator=(const C&); }; const C& C::operator=(const C& other) { if (this != &other) { this->~C(); // lifetime of *this ends new (this) C(other); // new object of type C created f(); // well-defined } return *this; } C c1; C c2; c1 = c2; // well-defined c1.f(); // well-defined; c1 refers to a new object of type C
|
上記の条件が満たされない場合でも、ポインタ最適化バリア struct A { virtual int transmogrify(); }; struct B : A { int transmogrify() override { ::new(this) A; return 2; } }; inline int A::transmogrify() { ::new(this) B; return 1; } void test() { A i; int n = i.transmogrify(); // int m = i.transmogrify(); // undefined behavior: // the new A object is a base subobject, while the old one is a complete object int m = std::launder(&i)->transmogrify(); // OK assert(m + n == 3); } |
(C++17以降) |
同様に、クラスメンバまたは配列要素のストレージにオブジェクトが作成された場合、作成されたオブジェクトが元のオブジェクトの包含オブジェクトのサブオブジェクト(メンバまたは要素)となるのは、以下の条件が満たされる場合に限る。
- 包含オブジェクトの生存期間が開始しており、終了していない。
- 新しいオブジェクトのストレージが元のオブジェクトのストレージと完全に重なる。
- 新しいオブジェクトが元のオブジェクトと同じ型である(cv修飾は無視される)。
|
そうでない場合、
|
(C++17以降) |
[編集] ストレージの提供
特殊なケースとして、unsigned char または std::byte(C++17以降)の配列内にオブジェクトを作成できる(この場合、配列がオブジェクトにストレージを提供すると言われる)。その条件は以下の通り。
- 配列の生存期間が開始しており、終了していない。
- 新しいオブジェクトのストレージが配列内に完全に収まる。
- 配列内にこれらの制約を満たす配列オブジェクトがネストされていない。
もしその配列の一部が以前に別のオブジェクトにストレージを提供していた場合、そのオブジェクトのストレージが再利用されたため、そのオブジェクトの生存期間は終了するが、配列自体の生存期間は終了しない(そのストレージは再利用されたとはみなされない)。
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 provides storage char *c = new (au.data) char(); // OK, ends lifetime of *p char *d = new (au.data + 1) char(); return *c + *d; // OK }
[編集] 生存期間外アクセス
オブジェクトの生存期間が開始する前であるが、オブジェクトが占有するストレージが割り当てられた後、あるいはオブジェクトの生存期間が終了した後で、オブジェクトが占有していたストレージが再利用または解放される前において、そのオブジェクトを識別するglvalue式の以下の使用の動作は未定義である。ただし、オブジェクトが構築中または破棄中の場合(個別のルールセットが適用される)を除く。
- Lvalueからrvalueへの変換(例:値を引数にとる関数への関数呼び出し)。
- 非静的データメンバへのアクセス、または非静的メンバ関数の呼び出し。
- 仮想基底クラスサブオブジェクトへの参照のバインディング。
-
dynamic_castまたはtypeid式。
上記のルールはポインタにも適用される(仮想基底への参照のバインディングは仮想基底へのポインタへの暗黙的な変換に置き換えられる)。加えて2つの追加ルールがある。
- オブジェクトなしのストレージへのポインタの
static_castは、(cv修飾されうる)void*にキャストする場合にのみ許可される。 - オブジェクトなしのストレージへのポインタで、(cv修飾されうる)void*にキャストされたものは、(cv修飾されうる)charまたは(cv修飾されうる)unsigned char、または(cv修飾されうる)std::byte(C++17以降)へのポインタにのみ
static_castできる。
構築中および破棄中には、通常、非静的メンバ関数の呼び出し、非静的データメンバへのアクセス、typeidおよびdynamic_castの使用が許可される。ただし、生存期間がまだ開始していない(構築中)か、すでに終了している(破棄中)ため、特定の操作のみが許可される。1つの制約については、構築中および破棄中の仮想関数呼び出しを参照。
[編集] 注釈
CWG issue 2256の解決までは、非クラスオブジェクト(ストレージ期間の終了)とクラスオブジェクト(構築の逆順)で生存期間の終了ルールが異なっていた。
struct A { int* p; ~A() { std::cout << *p; } // undefined behavior since CWG2256: n does not outlive a // well-defined until CWG2256: prints 123 }; void f() { A a; int n = 123; // if n did not outlive a, this could have been optimized out (dead store) a.p = &n; }
RU007の解決までは、const修飾型または参照型の非静的メンバがあると、包含オブジェクトが透過的に置き換えられなくなり、std::vectorやstd::dequeの実装が困難であった。
struct X { const int n; }; union U { X x; float f; }; void tong() { U u = { {1} }; u.f = 5.f; // OK: creates new subobject of 'u' X *p = new (&u.x) X {2}; // OK: creates new subobject of 'u' assert(p->n == 2); // OK assert(u.x.n == 2); // undefined until RU007: // 'u.x' does not name the new subobject assert(*std::launder(&u.x.n) == 2); // OK even until RU007 }
[編集] 欠陥報告
以下の動作変更を伴う欠陥報告が、以前に公開されたC++標準に遡って適用されました。
| DR | 適用対象 | 公開された動作 | 正しい動作 |
|---|---|---|---|
| CWG 119 | C++98 | 非自明なコンストラクタを持つクラス型のオブジェクトは コンストラクタの呼び出しが完了したときにのみ生存期間を開始できる |
生存期間も開始した 他の初期化のために |
| CWG 201 | C++98 | デフォルトコンストラクタの デフォルト引数内の一時オブジェクトの生存期間は、 配列の初期化が完了したときに終了する必要があった。 |
生存期間は、次の 要素を初期化する前に終了する (CWG issue 124も解決) CWG issue 124) |
| CWG 274 | C++98 | 生存期間外のオブジェクトを指定するlvalueは、static_castのオペランドとして使用できるのは、変換が最終的にcv非修飾のchar&またはunsigned char&に |
対する場合に限るとされていた cv修飾されたchar&とunsigned char&に対しても も許可 |
| CWG 597 | C++98 | 以下の動作は未定義であった 1. 生存期間外のオブジェクトへのポインタが暗黙的に 非仮想基底クラスへのポインタに変換される。 2. 生存期間外のオブジェクトを参照するlvalueが 非仮想基底クラスへの参照にバインドされる。 3. 生存期間外のオブジェクトを参照するlvalueが static_castのオペランドとして使用される(いくつかの例外を除く)。 |
符号ビットにシフトする動作は定義された |
| CWG 2012 | C++98 | 参照の生存期間はストレージ期間と一致すると規定されていたため、 extern参照は初期化子が実行される前に生きている必要があった。 |
生存期間は 初期化時に始まる。 |
| CWG 2107 | C++98 | CWG issue 124の解決がコピーコンストラクタに適用されていなかった。 | 適用済み |
| CWG 2256 | C++98 | 自明に破棄可能なオブジェクトの生存期間が他のオブジェクトと一貫していなかった。 | 整合性が取れた |
| CWG 2470 | C++98 | 複数の配列が同じオブジェクトにストレージを提供できるとされていた。 | 1つだけが提供する。 |
| CWG 2489 | C++98 | char[]はストレージを提供できないが、オブジェクトは そのストレージ内に暗黙的に作成される可能性がある。 |
オブジェクトは char[]のストレージ内に 暗黙的に作成することはできない。 |
| CWG 2527 | C++98 | ストレージの再利用のためにデストラクタが呼び出されず、 プログラムがその副作用に依存する場合、動作は未定義であった。 |
この場合、動作は 明確に定義される。 |
| CWG 2721 | C++98 | プレイスメントnewの場合、ストレージ再利用の正確な時点が不明確だった。 | 明確化された |
| CWG 2849 | C++23 | 関数パラメータオブジェクトは一時オブジェクトとみなされていた。 範囲ベースforループの一時オブジェクト生存期間延長のために |
一時オブジェクトとは みなされない。 |
| CWG 2854 | C++98 | 例外オブジェクトは一時オブジェクトであった。 | ならない みなされない。 |
| CWG 2867 | C++17 | 構造化束縛宣言で作成された一時オブジェクトの 生存期間は延長されていなかった。 |
宣言の終わりまで 延長される。 |
| P0137R1 | C++98 | unsigned charの配列内にオブジェクトを作成すると、そのストレージが再利用された。 | そのストレージは再利用されない。 |
| P0593R6 | C++98 | 擬似デストラクタ呼び出しには効果がなかった。 | オブジェクトを破棄する。 |
| P1971R0 | C++98 | const修飾型または参照型の非静的データメンバは、 包含オブジェクトが透過的に置き換えられるのを妨げていた。 |
制限が削除された。 |
| P2103R0 | C++98 | 透過的置換可能性は元の構造を維持することを要求していなかった。 | 要求するようになった |
[編集] 参照
- C++23標準 (ISO/IEC 14882:2024)
- 6.7.3 オブジェクトの生存期間 [basic.life]
- 11.9.5 構築と破棄 [class.cdtor]
- C++20 standard (ISO/IEC 14882:2020)
- 6.7.3 オブジェクトの生存期間 [basic.life]
- 11.10.4 構築と破棄 [class.cdtor]
- C++17 standard (ISO/IEC 14882:2017)
- 6.8 オブジェクトの生存期間 [basic.life]
- 15.7 構築と破棄 [class.cdtor]
- C++14 standard (ISO/IEC 14882:2014)
- 3 オブジェクトの生存期間 [basic.life]
- 12.7 構築と破棄 [class.cdtor]
- C++11 standard (ISO/IEC 14882:2011)
- 3.8 オブジェクトの生存期間 [basic.life]
- 12.7 構築と破棄 [class.cdtor]
- C++03 標準 (ISO/IEC 14882:2003)
- 3.8 オブジェクトの生存期間 [basic.life]
- 12.7 構築と破棄 [class.cdtor]
- C++98 標準 (ISO/IEC 14882:1998)
- 3.8 オブジェクトの生存期間 [basic.life]
- 12.7 構築と破棄 [class.cdtor]
[編集] 関連項目
| C ドキュメントの生存期間
|