デストラクタ
デストラクタはオブジェクトの生存期間が終了したときに呼ばれる特別なメンバ関数です。 デストラクタの目的はオブジェクトがその生存期間中に取得したリソースを解放することです。
目次 |
[編集] 構文
~ class_name ();
|
(1) | ||||||||
virtual ~ class_name ();
|
(2) | ||||||||
decl-specifier-seq(オプション) ~ class_name () = default;
|
(3) | (C++11以上) | |||||||
decl-specifier-seq(オプション) ~ class_name () = delete;
|
(4) | (C++11以上) | |||||||
attr(オプション) decl-specifier-seq(オプション) id-expression ( void (オプション) ) except(オプション) attr(オプション) ;
|
(5) | ||||||||
decl-specifier-seq | - | friend 、 inline 、 virtual または何もなし (戻り値の型は指定できません)。
| ||
id-expression | - | クラス定義内では、シンボル ~ の後に class_name が続いたもの。 クラステンプレート内では、シンボル ~ の後にテンプレートの現在の実体化の名前が続いたもの。 名前空間スコープまたは異なるクラス内の friend 宣言では、 nested-name-specifier の後にシンボル ~ が続いたもののの後に nested-name-specifier によって表されるものと同じクラスの class_name が続いたもの。 いずれの場合でも、名前は typedef ではなくクラスまたはテンプレートの実際の名前でなければなりません。 id 式の全体を括弧で囲っても良��、その意味は変わりません。
| ||
attr(C++11) | - | オプショナルな任意個の属性の並び。 | ||
except | - | あらゆる関数宣言の場合と同様の例外指定 (動的例外指定(非推奨)(C++17で削除)または noexcept 指定子(C++11)のいずれか)。
|
[編集] 説明
デストラクタはオブジェクトの生存期間が終了したときに呼ばれます。 これには以下が含まれます。
|
(C++11以上) |
- スコープの終了 (自動記憶域期間を持つオブジェクトおよび参照への束縛によって生存が延長された一時オブジェクトの場合)。
- delete 式 (動的記憶域期間を持つオブジェクトの場合)。
- 完全式の終了 (名前のない一時オブジェクトの場合)。
- スタックの巻き戻し (例外がキャッチされずにそのブロックを脱出したときの自動記憶域期間を持つオブジェクトの場合)。
デストラクタは、例えば配置 new を用いて構築されたオブジェクトを破棄するために、直接呼ぶこともできます。 また、アロケータを通して構築されたオブジェクトを破棄するために、 std::allocator::destroy() のようなアロケータのメンバ関数を通して呼ぶこともできます。 ローカル変数のような普通のオブジェクトに対してデストラクタを直接呼ぶと、そのデストラクタがそのスコープの終わりで再度呼ばれたとき、未定義動作を発生させることに注意してください。
総称の文脈では、デストラクタ呼び出しの構文を非クラス型のオブジェクトに対して使用できます。 これは疑似デストラクタ呼び出しと言います。 メンバアクセス演算子を参照してください。
[編集] 暗黙に宣言されたデストラクタ
クラス型 (struct、 class または union) に対してユーザ宣言されたデストラクタが提供されない場合は、コンパイラがそのクラスの inline public
メンバとして常にデストラクタを宣言します。
あらゆる暗黙に宣言された特別なメンバ関数と同様に、暗黙に宣言されたデストラクタの例外指定は、いずれかの潜在的に構築される基底またはメンバのデストラクタが潜在的に例外を投げる (C++17以上)暗黙の定義が異なる例外指定を持つ関数を直接呼ぶ (C++17未満)場合を除いて、例外を投げません。 実際のところ、暗黙のデストラクタは、 noexcept(false)
なデストラクタを持つ基底またはメンバによってそのクラスが「汚染」されない限り、 noexcept
です。
[編集] 削除された暗黙に宣言されたデストラクタ
クラス T
に対する暗黙に宣言されたまたはデフォルト化されたデストラクタは、以下のいずれかが真の場合、未定義です (C++11未満)削除されたものとして定義されます (C++11以上)。
-
T
が破棄できない (削除されたまたはアクセス不可能なデストラクタを持つ) 非静的データメンバを持つ。 -
T
が破棄できない (削除されたまたはアクセス不可能なデストラクタを持つ) 直接または仮想の基底クラスを持つ。
|
(C++11以上) |
- 暗黙に宣言されたデストラクタが (基底クラスが仮想デストラクタを持つために) 仮想であり、破棄関数 (operator delete()) の名前探索の結果が曖昧な、削除された、またはアクセス不可能な関数の呼び出しである。
[編集] トリビアルなデストラクタ
以下のすべてが真の場合、クラス T
に対するデストラクタはトリビアルです。
- デストラクタがユーザ提供されない (つまり、暗黙に宣言されたか、その最初の宣言において明示的にデフォルト化されたものとして定義されたかの、いずれか)。
- デストラクタが仮想でない (つまり、基底クラスのデストラクタが仮想でない)。
- すべての直接の基底クラスがトリビアルなデストラクタを持つ。
- クラス型 (またはクラスの配列型) のすべての非静的データメンバがトリビアルなデストラクタを持つ。
トリビアルなデストラクタは、何も行わないデストラクタです。 トリビアルなデストラクタを持つオブジェクトは delete 式を要求せず、その記憶域を単に解放することによって処分できます。 C 言語と互換性のあるすべてのデータ型 (POD 型) はトリビアルに破棄可能です。
[編集] 暗黙に定義されたデストラクタ
暗黙に宣言されたデストラクタが削除されない場合は、 ODR 使用されたときにコンパイラによって暗黙に定義されます (つまり、関数の本体が生成されコンパイルされます)。 この暗黙に定義されたデストラクタは空の本体を持ちます。
[編集] 破棄シーケンス
ユーザ定義されたまたは暗黙に定義されたデストラクタの両方について、デストラクタの本体が実行された後、コンパイラはそのクラスのすべての非静的非変種メンバのデストラクタを宣言の逆順で呼び、その後、すべての直接の非仮想基底クラスのデストラクタを構築の逆順で呼び (それはさらにそのメンバおよび基底クラスのデストラクタを呼び、以下同様)、その後、このオブジェクトが最も派生したクラスであれば、すべての仮想基底のデストラクタを呼びます。
デストラクタが直接呼ばれる (例えば obj.~Foo();) ときでも、 ~Foo() 内の return 文は直ちには制御を呼び出し元へ戻しません。 そのすべてのメンバと基底のデストラクタを最初に呼びます。
[編集] 仮想デストラクタ
基底へのポインタを通したオブジェクトの削除は、その基底クラスのデストラクタが仮想でなければ、未定義動作を発生させます。
class Base { public: virtual ~Base() {} }; class Derived : public Base {}; Base* b = new Derived; delete b; // 安全。
基底クラスに対するデストラクタはパブリックかつ仮想またはプロテクデットかつ非仮想のいずれかでなければならない、というのが一般的なガイドラインです。
[編集] 純粋仮想デストラクタ
デストラクタは純粋仮想として宣言しても構いません。 例えば、抽象にする必要があるけれども純粋仮想として宣言できる適当な関数が他にない基底クラスの場合などです。 派生クラスが破棄されるときはすべての基底クラスのデストラクタが必ず呼ばれるため、そのようなデストラクタは定義を持たなければなりません。
class AbstractBase { public: virtual ~AbstractBase() = 0; }; AbstractBase::~AbstractBase() {} class Derived : public AbstractBase {}; // AbstractBase obj; // コンパイルエラー。 Derived obj; // OK。
[編集] 例外
他のあらゆる関数と同様に、デストラクタは例外投げることによって終了しても構いません (これは通常、明示的に noexcept(false) 宣言することを要求します) (C++11以上) が、このデストラクタがたまたまスタックの巻き戻し中に呼ばれていた場合は、 std::terminate が呼ばれます。
処理中のスタック巻き戻しを検出するために時々 std::uncaught_exception が使われることがありますが、例外を投げることによる終了を任意のデストラクタに認めるのは一般的には悪い習慣であると考えられています。 とはいっても、 SOCI や Galera 3 などのいくつかのライブラリは、完全式の終了時に例外を投げるために名前のない一時オブジェクトのデストラクタの能力に依存しており、この機能を使用しています。
[編集] 例
出力:
ctor a0 ctor a1 ctor a2 ctor a3 dtor a2 dtor a3 dtor a1 dtor a0