仮想関数を使用しているときの破壊の順序を教えてください。基本クラスから始まり、次に派生クラスですか?
8 に答える
仮想関数がオブジェクトの破棄順序をどのように変更するかがわからないため、仮想継承シナリオでの基本クラスとデータメンバーの破棄順序を参照していると思います。
サブオブジェクトが構築されます
- 基本クラスは、ほとんどの基本から最も派生したものまで構築されます。
- 複数の基本クラスは、基本クラスとして宣言された順序で作成されます。
- 仮想基本クラスは、他のすべてのクラスよりも先に構築され、その中でも上記の2つのルールを順守します。
- データメンバーは、囲んでいるオブジェクトのコンストラクターの本体が実行される前に、宣言順に作成されます。
破壊は単に建設の反対であるため、上記を覚えるだけで済みます。
ただし、上記の4つのルールは、それが理にかなっているため、この順序になっています。この順序が理にかなっている理由を理解すれば、これらの4つのルールを覚える必要はありませんが、理解から推測することができます(私が行ったように)。それでは、その順序を調べてみましょう。
- 基本クラスが派生クラスのコンストラクターから提供するサービスを使用することをお勧めします。もちろん、実際に構築される前に(基本)クラスオブジェクトを使用することはできません。したがって、派生クラスを作成するときは、基本クラスを作成しておく必要があります。(ちなみに、これは、コンストラクター内から仮想関数ディスパッチが完全に機能しない理由も説明しています。サブオブジェクトが構築されると、基本クラスのサブオブジェクトのみが既に構築されています。派生クラスのサブオブジェクトはまだ構築されていません。したがって、仮想関数の呼び出しを派生クラスにディスパッチしてはなりません。いつものように、コンストラクターは同じですが、逆になります。)
- 複数の基本クラスが等しい兄弟であるため、いくつかの順序を任意に選択する必要がありました。最終的に、宣言の順序は最も簡単に使用できる順序です。等しい兄弟であるデータメンバーは、同じ(多かれ少なかれ恣意的な)宣言順の規則に従います。
- 仮想基本クラスは奇妙な獣です。仮想基本クラスのサブオブジェクトは常に1つしかないため、最も派生したクラスのコンストラクターから、常に最初に構築する必要があるという特別なルールがあります。(これが、仮想基本クラスが、データがなく、デフォルトのコンストラクターのみを持つ抽象基本クラスとして最適に機能する理由です。)
デストラクタを仮想として正しく宣言したと仮定します。
次に、破壊は建設の正反対の順序で行われます。
一般的に、これは次のようになります。
A)最も派生したクラスから始めます。
B)以下を再帰的に繰り返します。
1)デストラクタコードを実行します。
2)各メンバーのデストラクタを(作成と逆の順序で
)実行します。3)親クラスのデストラクタを実行します。(作成の逆順で複数ある場合)
ただし、仮想継承を使用する場合、基本クラスの構築の順序は通常と同じではないため、状況は少し異なります。しかし、破壊の順序は常に建設の順序の逆です。
破壊順序は逆方向の建設順序です。最近、任意の階層の構築順序を表示するための小さなツールを作成しました。ここを見て:
図では、番号の小さいノードが最初に構築され、最後に破棄されます。
セクション12.6.2/5:
初期化は次の順序で進行します。
- まず、以下で説明する最も派生したクラスのコンストラクターの場合のみ、仮想基本クラスは、基本クラスの有向非巡回グラフの深さ優先の左から右へのトラバースに表示される順序で初期化されます。 -to-right」は、派生クラスbase-specifier-listでの基本クラス名の出現順序です。
- 次に、直接基本クラスは、(mem-initializersの順序に関係なく)base-specifier-listに表示される宣言順序で初期化されます。
- 次に、非静的データメンバーは、クラス定義で宣言された順序で初期化されます(ここでも、mem-initializersの順序に関係なく)。—最後に、コンストラクターの本体が実行されます。
[注:初期化の逆の順序でベースおよびメンバーのサブオブジェクトが確実に破棄されるように、宣言の順序が義務付けられています。]
一方、仮想関数は破棄の順序に違いはありませんが、仮想基本クラスは違いがあります。
仮想基本クラスがない場合、派生クラスは常に基本クラスの前に破棄されます。これは、それらが構築される逆の順序です。
最も派生したクラスの場合、仮想基本クラスが最初に構築され、他の基本クラスの前に、最も派生したクラス自体の前に構築されます。破壊は逆の順序で発生します。これは、仮想ベースが破壊される最も派生したクラスでない場合、仮想ベースから仮想的に派生するクラスの後に仮想ベースが破壊される可能性があることを意味します。これは、直接の基本クラスでは発生しません。
コンストラクターとは逆の方法です。したがって、最初に導出されます。
下から上への場合の破壊の順序。(派生からベースへ)
簡単な答え:コンストラクターの順序とは正反対です。
長い答え:「最も派生した」クラスがDであると仮定します。これは、最初に作成された実際のオブジェクトがクラスDであり、DがB1とB2から乗算(および非仮想)を継承することを意味します。ほとんどの派生クラスDに対応するサブオブジェクトが最初に実行され、次に非仮想基本クラスのdtorが逆宣言順に実行されます。したがって、デストラクタの順序はD、B2、B1になります。このルールは再帰的に適用されます。たとえば、B1がB1aとB1bを継承し、B2がB2aとB2bを継承する場合、最終的な順序はD、B2、B2b、B2a、B1、B1b、B1aです。
C++に関するよくある質問のセクション25を参照してください。
最初に派生、次にベース。非仮想の場合と違いはありません。
追記。継承メソッドと仮想メソッドがある場合は、デストラクタを仮想として宣言する必要があります。そうしないと、削除時に未定義の動作が発生する可能性があります。
たとえば、DerivedがBaseから派生し、次の行を使用してDerivedを割り当てたとします。
Base *o = new Derived();
delete(o);
このケースがコードで発生し、Baseに仮想デストラクタがない場合、結果の動作は未定義です。通常、Baseのデストラクタのみが呼び出されます。Baseポインタでdeleteを呼び出しているため、Derivedのデストラクタは呼び出されません。ただし、代わりにプログラムがクラッシュする可能性があります。未定義動作の領域に入ると、すべての賭けは無効になり、実行中のコードは運命づけられます。混乱を防ぐために、ベースデストラクタは仮想である必要があります。