デストラクタ
デストラクタは、オブジェクトの寿命が終わったときに呼び出される特別なメンバ関数です。デストラクタの目的は、オブジェクトがその寿命中に取得した可能性のあるリソースを解放することです。
|
デストラクタはコルーチンにはできません。 |
(C++20以降) |
目次 |
[編集] 構文
デストラクタ(C++20まで)候補デストラクタ(C++20以降) は、次の形式のメンバ関数宣言子を使用して宣言されます。
クラス名とチルダ ( パラメータリスト (任意) ) 例外仕様 (任意) 属性 (任意) |
|||||||||
| クラス名とチルダ | - | または、一次式、オプションで属性のリストが続き、(C++11以降)オプションで括弧のペアで囲まれた識別子 | ||||||
| parameter-list | - | パラメータリスト (空またはvoidである必要があります) | ||||||
| except | - |
| ||||||
| attr | - | (C++11以降) 属性のリスト |
デストラクタ宣言の宣言指定子で許可されるのは、候補(C++20以降) デストラクタ宣言のconstexpr、(C++11以降)friend、inline、およびvirtual (特に、戻り値の型は許可されません) です。
クラス名とチルダの識別子式は、次のいずれかの形式である必要があります。
- それ以外の場合、識別子式は、修飾識別子の末尾の非修飾識別子が~で、その後に修飾識別子の非終端部分によって指名されたクラスの注入クラス名が続く、修飾識別子です。
[編集] 説明
デストラクタは、オブジェクトの寿命が終わるときに暗黙的に呼び出されます。これには以下が含まれます。
|
(C++11以降) |
- 自動記憶域期間を持つオブジェクトおよび参照へのバインディングによって寿命が延長された一時オブジェクトのスコープ終了
- 動的記憶域期間を持つオブジェクトのdelete 式
- 無名一時オブジェクトの完全な式の終了
- 例外がブロックをエスケープし、捕捉されなかった場合の自動記憶域期間を持つオブジェクトのスタック巻き戻し。
デストラクタは明示的に呼び出すこともできます。
候補デストラクタクラスは1つ以上の候補デストラクタを持つことができ、そのうちの1つがクラスのデストラクタとして選択されます。 どの候補デストラクタがデストラクタであるかを決定するために、クラス定義の終わりに、クラスで宣言された空の引数リストを持つ候補デストラクタ間でオーバーロード解決が実行されます。オーバーロード解決が失敗した場合、プログラムは不正な形式になります。デストラクタの選択は、選択されたデストラクタをodr-useせず、選択されたデストラクタは削除される場合があります。 すべての候補デストラクタは特殊メンバ関数です。クラス このコードを実行 #include <cstdio> #include <type_traits> template<typename T> struct A { ~A() requires std::is_integral_v<T> { std::puts("~A, T is integral"); } ~A() requires std::is_pointer_v<T> { std::puts("~A, T is a pointer"); } ~A() { std::puts("~A, T is anything else"); } }; int main() { A<int> a; A<int*> b; A<float> c; } 出力 ~A, T is anything else ~A, T is a pointer ~A, T is integral |
(C++20以降) |
[編集] 潜在的に呼び出されるデストラクタ
クラスTのデストラクタは、次のような状況で*潜在的に呼び出されます*。
- 明示的または暗黙的に呼び出される。
- new 式が型
Tのオブジェクトの配列を作成する。 - return 文の結果オブジェクトが型
Tである。 - 配列が集計初期化中にあり、その要素型が
Tである。 - クラスオブジェクトが集計初期化中にあり、
Tが匿名共用体型ではないメンバTを持っている。 - 潜在的に構築されるサブオブジェクトが、非委譲(C++11以降)コンストラクタにおいて型
Tである。 - 型
Tの例外オブジェクトが構築される。
潜在的に呼び出されるデストラクタが削除されているか、(C++11以降)呼び出しのコンテキストからアクセスできない場合、プログラムは不正な形式になります。
[編集] 暗黙宣言デストラクタ
ユーザー宣言された候補(C++20以降)デストラクタがクラス型に提供されていない場合、コンパイラは常にそのクラスのinline publicメンバとしてデストラクタを宣言します。
暗黙宣言された特殊メンバ関数と同様に、暗黙宣言されたデストラクタの例外仕様は、潜在的に構築される基底クラスまたはメンバのデストラクタが例外を投げうる場合(C++17以降)暗黙定義が異なる例外仕様を持つ関数を直接呼び出す場合(C++17まで)を除き、例外を投げません。実際には、暗黙デストラクタは、クラスが例外を投げないデストラクタを持つ基底クラスまたはメンバによって「汚染」されていない限り、noexceptです。
[編集] 暗黙定義デストラクタ
暗黙宣言されたデストラクタが削除されていない場合、それはodr-usedされると、コンパイラによって暗黙的に定義されます(つまり、関数本体が生成されコンパイルされます)。この暗黙定義されたデストラクタは空の本体を持ちます。
|
これがconstexprデストラクタ(C++23まで)constexpr関数(C++23以降) の要件を満たす場合、生成されたデストラクタはconstexpr になります。 |
(C++20以降) |
削除済みデストラクタクラス
|
(C++11以降) |
[編集] 自明デストラクタ
クラスTのデストラクタは、以下のすべての条件が満たされた場合に自明とみなされます。
- デストラクタは暗黙宣言されている(C++11まで)ユーザー提供されていない(C++11以降)。
- デストラクタは仮想ではない。
- すべての直接基底クラスは自明なデストラクタを持つ。
|
(C++26まで) |
|
(C++26以降) |
自明なデストラクタは、何のアクションも実行しないデストラクタです。自明なデストラクタを持つオブジェクトは、delete 式を必要とせず、単にストレージを解放することで破棄できます。C言語と互換性のあるすべてのデータ型(POD型)は、自明に破棄可能です。
[編集] 破棄シーケンス
ユーザー定義または暗黙定義されたデストラクタの場合、デストラクタ本体の実行と、本体内で割り当てられた自動オブジェクトの破棄後、コンパイラはクラスのすべての非静的非バリアントデータメンバのデストラクタを宣言順の逆順で呼び出し、次にすべての直接非仮想基底クラスのデストラクタを構築順の逆順で呼び出します(これらはさらにメンバや基底クラスのデストラクタなどを呼び出します)。その後、このオブジェクトが最も派生したクラスである場合、すべての仮想基底クラスのデストラクタを呼び出します。
デストラクタが直接呼び出された場合(例:obj.~Foo();)でも、~Foo() のreturn 文はすぐに呼び出し元に制御を返しません。まず、これらのすべてのメンバおよび基底デストラクタを呼び出します。
[編集] 仮想デストラクタ
基底クラスへのポインタを通じてオブジェクトを削除すると、基底クラスのデストラクタが仮想でない限り未定義の動作が発生します。
class Base { public: virtual ~Base() {} }; class Derived : public Base {}; Base* b = new Derived; delete b; // safe
一般的なガイドラインとして、基底クラスのデストラクタはパブリックかつ仮想、またはプロテクテッドかつ非仮想のいずれかであるべきです。
[編集] 純粋仮想デストラクタ
候補(C++20以降) デストラクタは、純粋仮想として宣言されることがあります。これは、抽象化する必要があるが、純粋仮想として宣言できる他の適切な関数がない基底クラスなどで使用されます。純粋仮想デストラクタは定義を持つ必要があります。なぜなら、派生クラスが破棄される際にすべての基底クラスのデストラクタは常に呼び出されるからです。
class AbstractBase { public: virtual ~AbstractBase() = 0; }; AbstractBase::~AbstractBase() {} class Derived : public AbstractBase {}; // AbstractBase obj; // compiler error Derived obj; // OK
[編集] 例外
他の関数と同様に、デストラクタは例外をスローして終了することができます(これは通常、明示的にnoexcept(false)として宣言する必要があり(C++11以降)ます)。ただし、このデストラクタがスタック巻き戻し中に呼び出された場合、代わりにstd::terminateが呼び出されます。
std::uncaught_exceptionsはスタック巻き戻しが進行中であることを検出するために使用されることがありますが、デストラクタが例外をスローして終了することは一般的に悪い実践と見なされています。ただし、この機能は、一時オブジェクトのデストラクタが一時オブジェクトを構築した完全な式の終わりに例外をスローできる能力に依存しているSOCIやGalera 3などの一部のライブラリで使用されています。
ライブラリ基本TS v3 のstd::experimental::scope_success は、例外を投げうるデストラクタを持つことがあり、スコープが正常に終了し、終了関数が例外をスローした場合に例外をスローします。
[編集] 注記
ローカル変数のような通常のオブジェクトに対してデストラクタを直接呼び出すと、スコープの終わりにデストラクタが再度呼び出されたときに未定義の動作が発生します。
ジェネリックなコンテキストでは、クラス型でないオブジェクトに対してデストラクタ呼び出し構文を使用できます。これは擬似デストラクタ呼び出しとして知られています。詳細はメンバアクセス演算子を参照してください。
| 機能テストマクロ | 値 | 規格 | 機能 |
|---|---|---|---|
__cpp_trivial_union |
202502L |
(C++26) | 共用体の特殊メンバ関数の自明性要件の緩和 |
[編集] 例
#include <iostream> struct A { int i; A(int num) : i(num) { std::cout << "ctor a" << i << '\n'; } (~A)() // but usually ~A() { std::cout << "dtor a" << i << '\n'; } }; A a0(0); int main() { A a1(1); A* p; { // nested scope A a2(2); p = new A(3); } // a2 out of scope delete p; // calls the destructor of a3 }
出力
ctor a0 ctor a1 ctor a2 ctor a3 dtor a2 dtor a3 dtor a1 dtor a0
[編集] 不具合報告
以下の動作変更を伴う欠陥報告が、以前に公開されたC++標準に遡って適用されました。
| DR | 適用対象 | 公開された動作 | 正しい動作 |
|---|---|---|---|
| CWG 193 | C++98 | デストラクタ内の自動オブジェクトが クラスの基底およびメンバサブオブジェクトの 破棄の前か後か |
が未指定だった それらのサブオブジェクトを 破棄する前に |
| 破棄する | C++98 | CWG 344 デストラクタの宣言子構文が欠陥があった(CWG issue 194およびCWG issue 263と同じ問題) |
構文を特殊な 関数宣言子構文に変更 |
| CWG 1241 | C++98 | 静的メンバがデストラクタ実行 直後に破棄される可能性があった |
非静的メンバのみ 破棄する |
| CWG 1353 | C++98 | 暗黙宣言されたデストラクタが 未定義となる条件が多次元配列型を考慮していなかった |
これらの型を考慮する |
| CWG 1435 | C++98 | デストラクタの 宣言子構文における「クラス名」の意味が不明確だった |
構文を特殊な 関数宣言子構文に変更 |
| CWG 2180 | C++98 | 最も派生したクラスではないクラスの デストラクタが、その仮想直接基底クラスのデストラクタを呼び出す |
それらのデストラクタは呼び出されない |
| CWG 2807 | C++20 | 宣言指定子にconstevalを含めることができた | 禁止された |