次のコードはコンパイルされません。
class A {
public:
A(int) {}
};
class B: virtual public A {
public:
B(): A(0) {}
};
// most derived class
class C: public B {
public:
C() {} // wrong!!!
};
A
のコンストラクタ初期化リストでC
のコンストラクタを呼び出すと、次のようになります。
// most derived class
class C: public B {
public:
C(): A(0) {} // OK!!!
};
それは機能します。
どうやら、その理由は仮想基本クラスは常にほとんどの派生クラスによって構築されなければならないからです。
この制限の背後にある理由がわかりません。
これを回避するため:
class A {
public:
A(int) {}
};
class B0: virtual public A {
public:
B0(): A(0) {}
};
class B1: virtual public A {
public:
B1(): A(1) {}
};
class C: public B0, public B1 {
public:
C() {} // How is A constructed? A(0) from B0 or A(1) from B1?
};
仮想的に継承された基本クラスを持つクラス階層では、基本クラスは複数のクラスによって共有されるか、共有される可能性があるためです(たとえば、同じ基本クラスが複数のクラスによって継承されるダイヤモンド継承の場合)。つまり、仮想的に継承された基本クラスのコピーは1つだけです。つまり、基本クラスを最初に構築する必要があります。最終的には、派生クラスが指定された基本クラスをインスタンス化する必要があることを意味します。
例えば:
class A;
class B1 : virtual A;
class B2 : virtual A;
class C: B1,B2 // A is shared, and would have one copy only.
このルールはエラーが発生しやすく面倒だと思います(しかし、多重継承のどの部分がそうではありませんか?)。
ただし、論理的に課せられる構築の順序は、通常の(非仮想)継承の場合とは異なる必要があります。仮想を除いたAjayの例を考えてみましょう。
class A;
class B1 : A;
class B2 : A;
class C: B1,B2
この場合、Cごとに2つのAsが構築され、1つはB1の構築の一部として、もう1つはB2の構築の一部として構築されます。 Bクラスのコードがそれを担当し、それを実行できます。イベントの順序は次のとおりです。
Start C ctor
Start B1 ctor
A ctor (in B's ctor code)
End B1 ctor
Start B2 ctor
A ctor (in B's ctor code)
End B2 ctor
End C ctor
ここで、仮想継承について考えてみましょう。
class A;
class B1 : virtual A;
class B2 : virtual A;
class C: B1,B2
イベントの1つの順序は
Start C ctor
Start B1 ctor
// NO A ctor
End B1 ctor
Start B2 ctor
// NO A ctor
End B2 ctor
A ctor // not B's code!
End C ctor
AがB1とB2の前に構築されている可能性はありますが、それは関係ありません。重要なことは、他の基本クラスの構築中にAの構築が行われないことです。タイプAの仮想的に継承された基本クラスサブオブジェクトは最も派生したクラスの一部であり、その制御下にあるであり、B1からアクセスできないため、そのコードは単純に実行されず、実行できません。 B2のコード;実際、B1またはB2が構築されている時点では、Cは完全には構築されておらず、CにAを作成しようとする可能性があります。