Lifetime
すべての object および reference には 生存期間 があります。これは実行時プロパティです:あらゆるオブジェクトまたは参照について、その生存期間が開始するプログラム実行の時点があり、終了する瞬間があります。
オブジェクトの生存期間は以下の時に開始されます:
- その型に適切なアライメントとサイズを持つストレージが取得され、
- その初期化(もしあれば)が完了している(コンストラクタがない場合や デフォルト初期化 によるもの、あるいは トリビアルデフォルトコンストラクタ によるものを含む)。ただし、
-
- オブジェクトが 共用体メンバー またはその部分オブジェクトである場合、その生存期間は、その共用体メンバーが共用体内で初期化されたメンバーである場合、またはアクティブにされた場合にのみ開始されます。
- オブジェクトが共用体オブジェクト内にネストされている場合、包含する共用体オブジェクトが自明な特殊メンバー関数によって代入または構築されると、その生存期間が開始されることがあります。
- 配列オブジェクトの生存期間は、 std::allocator::allocate によって割り当てられた場合にも開始されることがあります。
一部の操作は、指定された記憶域領域内で 暗黙的にオブジェクトを作成 し、その生存期間を開始します。暗黙的に作成されたオブジェクトの副オブジェクトが暗黙的生存期間型でない場合、その生存期間は暗黙的に開始されません。
オブジェクトの寿命が終了するのは以下の場合です:
- 非クラス型の場合、オブジェクトは破棄される(擬似デストラクタ呼び出しを介して行われる可能性あり)、または
- クラス型の場合、 destructor の呼び出しが開始される、または
- オブジェクトが占有していた記憶域が解放される、またはその内部にネストされていないオブジェクトによって 再利用 される。
オブジェクトの生存期間は、そのストレージの生存期間と等しいか、またはその内部にネストされています。詳細は storage duration を参照してください。
参照の生存期間は、 reference の初期化が完了した時点で始まり、スカラーオブジェクトであるかのように終了します。
注意: 参照先オブジェクトの寿命が参照の寿命よりも先に終了する可能性があり、 ダングリング参照 が発生する可能性があります。
非静的データメンバと基底サブオブジェクトの生存期間は、 class initialization order に従って開始および終了します。
目次 |
一時オブジェクトの寿命
一時オブジェクトは、 prvalueが materialized されてglvalueとして使用可能になる際に生成され、これは以下の状況で発生します (since C++17) :
|
(C++11以降) |
|
(C++17まで) | ||
一時オブジェクトの実体化は、不必要な一時オブジェクトの作成を避けるために可能な限り遅延される: コピー省略 を参照。 |
(C++17以降) |
|
この自由度は、オブジェクトをレジスタで関数に渡したり返したりできるようにするために与えられています。 |
(C++17以降) |
すべての一時オブジェクトは、それらが作成された位置を(字句的に)含む full-expression の評価の最終ステップとして破棄され、複数の一時オブジェクトが作成された場合、それらは作成順と逆の順序で破棄されます。これは、その評価が例外をスローして終了した場合でも同様です。
以下はその例外です:
- 一時オブジェクトの寿命は参照へのバインドによって延長される場合があります。詳細は reference initialization を参照してください。
- 配列の要素を初期化またはコピーするために使用されるデフォルトコンストラクタまたはコピーコンストラクタのデフォルト引数を評価する際に作成される一時オブジェクトの寿命は、配列の次の要素の初期化が開始される前に終了します。
|
(C++17以降) |
|
(C++23以降) |
ストレージ再利用
プログラムは、オブジェクトが 自明に破棄可能 である場合(プログラムの正しい動作がデストラクタに依存する可能性があることに注意)、そのオブジェクトの寿命を終了させるためにデストラクタを呼び出す必要はありません。しかし、プログラムが非自明なデストラクタを持つオブジェクト(変数である場合)の寿命を明示的に終了させる場合、デストラクタが暗黙的に呼び出される前(すなわち、自動オブジェクトの場合はスコープ終了または例外による、 new 配置 スレッドローカルオブジェクトの場合はスレッド終了による、 (C++11以降) 静的オブジェクトの場合はプログラム終了による)に、同じ型の新しいオブジェクトをその場で構築する(例:配置 new を介して)ことを保証しなければなりません。そうでない場合、動作は未定義です。
class T {}; // trivial(自明) struct B { ~B() {} // non-trivial(非自明) }; void x() { long long n; // automatic(自動)、trivial(自明) new (&n) double(3.14); // 異なる型での再利用は問題なし } // 問題なし void h() { B b; // automatic(自動)non-trivially destructible(非自明なデストラクタ) b.~B(); // 寿命終了(副作用がないため必須ではない) new (&b) T; // 誤った型:デストラクタが呼び出されるまでは問題なし } // デストラクタが呼び出される:未定義動作
静的 , スレッドローカル, (C++11以降) または自動ストレージ期間を持つconst完全オブジェクトが占有していた(または占有している)ストレージを再利用することは未定義動作です。このようなオブジェクトは読み取り専用メモリに格納されている可能性があるためです:
struct B { B(); // 非自明 ~B(); // 非自明 }; const B b; // const static void h() { b.~B(); // bの生存期間を終了 new (const_cast<B*>(&b)) const B; // 未定義動作: constオブジェクトの再利用の試み }
new 式 を評価する際、ストレージは アロケーション関数 から返された後に再利用されると見なされますが、new式の initializer の評価前になります:
struct S { int m; }; void f() { S x{1}; new(&x) S(x.m); // 未定義動作: ストレージが再利用されている }
あるオブジェクトが占有していたアドレスに新しいオブジェクトが作成された場合、元のオブジェクトに対するすべてのポインタ、参照、および名前は自動的に新しいオブジェクトを参照するようになり、新しいオブジェクトの生存期間が開始されると、新しいオブジェクトを操作するために使用できるが、これは元のオブジェクトが新しいオブジェクトによって透過的に置換可能な場合に限られる。
以下のすべての条件が満たされる場合、オブジェクト 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(); // *thisの生存期間が終了 new (this) C(other); // 型Cの新しいオブジェクトが作成される f(); // 明確に定義された動作 } return *this; } C c1; C c2; c1 = c2; // 明確に定義された動作 c1.f(); // 明確に定義された動作; c1は型Cの新しいオブジェクトを参照
|
上記の条件が満たされない場合でも、ポインタ最適化バリア std::launder を適用することで、新しいオブジェクトへの有効なポインタを取得できる可能性があります: 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修飾を無視する)
|
それ以外の場合、元の部分オブジェクトの名前は std::launder なしでは新しいオブジェクトにアクセスするために使用できません:
|
(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がストレージを提供 char *c = new (au.data) char(); // OK、*pの寿命を終了させる char *d = new (au.data + 1) char(); return *c + *d; // OK }
生存期間外へのアクセス
オブジェクトの生存期間が開始する前で、かつそのオブジェクトが占有する記憶域が割り当てられた後、または、オブジェクトの生存期間が終了した後で、かつそのオブジェクトが占有していた記憶域が再利用または解放される前に、そのオブジェクトを識別するglvalue式の以下の使用法の動作は、オブジェクトが構築中または破棄中である場合を除き(別個の規則が適用されます)、未定義です:
- オブジェクトにアクセスする。
- 非静的データメンバへのアクセス、または非静的メンバ関数の呼び出し。
- 仮想基底クラスのサブオブジェクトへの参照をバインドする。
-
dynamic_castまたはtypeid式。
上記の規則はポインタにも適用されます(仮想基底クラスへの参照の束縛は、仮想基底クラスへのポインタへの暗黙変換で置き換えられます)。これに加えて2つの追加規則があります:
-
static_castオブジェクトを持たないストレージへのポインタの変換は、(possibly cv-qualified) void * へのキャストの場合にのみ許可されます。 -
オブジェクトを持たないストレージへのポインタで、possibly cv-qualified
void
*
にキャストされたものは、possibly cv-qualified
char
へのポインタ、またはpossibly cv-qualified
unsigned
char
、またはpossibly cv-qualified
std::byte
(C++17以降)
へのポインタにのみ
static_castできます。
構築および破壊中は、一般的に非静的メンバ関数の呼び出し、非静的データメンバへのアクセス、
typeid
および
dynamic_cast
の使用が許可されています。しかし、ライフタイムがまだ開始されていない(構築中)か、すでに終了している(破壊中)ため、特定の操作のみが許可されます。一つの制限については、
構築および破壊中の仮想関数呼び出し
を参照してください。
注記
CWG issue 2256 が解決されるまで、生存期間の終了規則は非クラスオブジェクト(ストレージ期間の終了)とクラスオブジェクト(構築の逆順)で異なります:
struct A { int* p; ~A() { std::cout << *p; } // CWG2256以降は未定義動作: nはaよりも長生きしない // CWG2256までは明確に定義: 123を出力 }; void f() { A a; int n = 123; // nがaよりも長生きしない場合、これは最適化で除去される可能性がある(デッドストア) 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: 'u'の新しいサブオブジェクトを作成 X *p = new (&u.x) X {2}; // OK: 'u'の新しいサブオブジェクトを作成 assert(p->n == 2); // OK assert(u.x.n == 2); // RU007までは未定義: // 'u.x'は新しいサブオブジェクトを指さない assert(*std::launder(&u.x.n) == 2); // RU007まででもOK }
不具合報告
以下の動作変更の欠陥報告書は、以前に公開されたC++規格に対して遡及的に適用されました。
| DR | 適用対象 | 公開時の動作 | 正しい動作 |
|---|---|---|---|
| CWG 119 | C++98 |
非トリビアルコンストラクタを持つクラス型のオブジェクトは
コンストラクタ呼び出しが完了した時点でのみ生存期間が開始する |
その他の初期化でも
生存期間が開始する |
| CWG 201 | C++98 |
デフォルトコンストラクタのデフォルト引数内の一時オブジェクトの
生存期間は配列の初期化が完了した時点で終了する必要があった |
次の要素の初期化前に
生存期間が終了する ( CWG issue 124 も解決) |
| CWG 274 | C++98 |
生存期間外のオブジェクトを指す左辺値は、変換が最終的に
cv-unqualified char & または unsigned char & になる場合にのみ static_castのオペランドとして使用可能 |
cv-qualified
char
&
および unsigned char & も許可される |
| CWG 597 | C++98 |
以下の動作は未定義であった:
1. 生存期間外のオブジェクトへのポインタが非仮想基底クラスへの ポインタに暗黙変換される 2. 生存期間外のオブジェクトを参照する左辺値が非仮想基底クラスへの 参照にバインドされる 3. 生存期間外のオブジェクトを参照する左辺値が 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標準 (ISO/IEC 14882:2020):
-
- 6.7.3 オブジェクト生存期間 [basic.life]
-
- 11.10.4 構築と破棄 [class.cdtor]
- C++17規格 (ISO/IEC 14882:2017):
-
- 6.8 オブジェクト生存期間 [basic.life]
-
- 15.7 構築と破棄 [class.cdtor]
- C++14 標準 (ISO/IEC 14882:2014):
-
- 3 オブジェクト生存期間 [basic.life]
-
- 12.7 構築と破棄 [class.cdtor]
- C++11標準 (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 documentation
for
Lifetime
|