C++のクラス階層では、ダイヤモンド問題が発生する可能性があります。これは、1つの基底クラスから2つの子クラスが継承され、それからさらにこれら2つから継承されるクラスが作成される場合です。この場合、派生クラスのオブジェクトは基底クラスの独立した2つのコピーを持つことになります。この問題を解決するためにC++では仮想継承が導入されました。
通常の多重継承を使用すると:
class A { public: int x; }; class B : public A {}; class C : public A {}; class D : public B, public C {};
オブジェクトDはAの2つのコピーを持ちます:1つはBを介し、もう1つはCを介して。このため、A::xへのアクセスの曖昧さと不必要なメモリの消費が発生します。
仮想継承は基底クラスの重複を排除します。基底クラスAの1つのインスタンスがすべての子クラスで使用されます:
class A { public: int x; }; class B : public virtual A {}; class C : public virtual A {}; class D : public B, public C {};
これでDはAのコピーを1つだけ持ち、A::xへのアクセスの曖昧さが解消されます。
主なポイント:
スコープ解決を通しての明示的な指定でダイヤモンド問題を解決できないのはなぜですか?
スコープ解決は基底クラスのメンバーへのアクセスの曖昧さを解決しますが、基底クラス自体の余分なコピーを取り除くことはできません。二重初期化とデータの二重保持の問題は残ります。
仮想継承の場合、コンストラクタはどのような順序で呼び出されますか?
仮想基底クラスのコンストラクタは最初に呼び出され、1回だけ呼ばれ、継承階層で最も最後のクラスのコンストラクタによって直接呼び出されます。
例:
class A { public: A() { std::cout << "A\n"; } }; class B : public virtual A { public: B() { std::cout << "B\n"; } }; class C : public virtual A { public: C() { std::cout << "C\n"; } }; class D : public B, public C { public: D() { std::cout << "D\n"; } }; D d; // 出力: A B C D
仮想継承は、宣言時と定義時の両方で必ず宣言する必要がありますか?
はい、仮想継承は関連する基底クラスから継承するすべての場所で指定する必要があります。そうしないと、コンパイルエラーが不可避になり、多重継承が通常のものになります。
仮想継承が使用されていない複雑な階層で、オブジェクトに2つの設定コピーが存在する:
利点:
欠点:
最上位の子クラスのコンストラクタが整合性のある状態で仮想継承が使用される:
利点:
欠点: