なぜ別の答えですか?
さて、SOに関する多くの投稿と外部の記事によると、ダイヤモンドの問題はA
2つではなく1つのインスタンス(の各親に対して1つD
)を作成することで解決され、あいまいさを解決しています。しかし、これではプロセスを包括的に理解できなかったため、次のような質問がさらに多くなりました。
- たとえば、異なるパラメーター()でパラメーター化されたコンストラクターを呼び出すなどの異なるインスタンスを作成しようとするとどう
B
なりますか?の一部になるために選択されるのはどのインスタンスですか?C
A
D::D(int x, int y): C(x), B(y) {}
A
D
- に非仮想継承を使用し
B
、仮想継承を使用するとC
どうなりますか?A
inの単一インスタンスを作成するのに十分D
ですか?
- 小さなダイヤモンドのパフォーマンスコストで他の欠点はなく、可能性のあるダイヤモンドの問題を解決するので、予防策としてこれからデフォルトで常に仮想継承を使用する必要がありますか?
コードサンプルを試さずに動作を予測できないことは、概念を理解していないことを意味します。以下は、仮想継承に頭を悩ますのに役立つものです。
ダブルA
まず、仮想継承なしのこのコードから始めましょう:
#include<iostream>
using namespace std;
class A {
public:
A() { cout << "A::A() "; }
A(int x) : m_x(x) { cout << "A::A(" << x << ") "; }
int getX() const { return m_x; }
private:
int m_x = 42;
};
class B : public A {
public:
B(int x):A(x) { cout << "B::B(" << x << ") "; }
};
class C : public A {
public:
C(int x):A(x) { cout << "C::C(" << x << ") "; }
};
class D : public C, public B {
public:
D(int x, int y): C(x), B(y) {
cout << "D::D(" << x << ", " << y << ") "; }
};
int main() {
cout << "Create b(2): " << endl;
B b(2); cout << endl << endl;
cout << "Create c(3): " << endl;
C c(3); cout << endl << endl;
cout << "Create d(2,3): " << endl;
D d(2, 3); cout << endl << endl;
// error: request for member 'getX' is ambiguous
//cout << "d.getX() = " << d.getX() << endl;
// error: 'A' is an ambiguous base of 'D'
//cout << "d.A::getX() = " << d.A::getX() << endl;
cout << "d.B::getX() = " << d.B::getX() << endl;
cout << "d.C::getX() = " << d.C::getX() << endl;
}
出力を見てみましょう。実行B b(2);
するとA(2)
、期待どおりに作成されますC c(3);
:
Create b(2):
A::A(2) B::B(2)
Create c(3):
A::A(3) C::C(3)
D d(2, 3);
との両方が必要でB
ありC
、それぞれが独自のを作成しているA
ので、ダブルA
インしていd
ます:
Create d(2,3):
A::A(2) C::C(2) A::A(3) B::B(3) D::D(2, 3)
d.getX()
コンパイラはA
メソッドを呼び出すインスタンスを選択できないため、これがコンパイルエラーを引き起こす理由です。それでも、選択した親クラスのメソッドを直接呼び出すことは可能です。
d.B::getX() = 3
d.C::getX() = 2
仮想性
次に、仮想継承を追加します。次の変更を加えた同じコードサンプルを使用します。
class B : virtual public A
...
class C : virtual public A
...
cout << "d.getX() = " << d.getX() << endl; //uncommented
cout << "d.A::getX() = " << d.A::getX() << endl; //uncommented
...
の作成にジャンプしましょうd
:
Create d(2,3):
A::A() C::C(2) B::B(3) D::D(2, 3)
ご覧のA
とおり、B
およびのコンストラクターから渡されたパラメーターを無視して、デフォルトのコンストラクターで作成されていますC
。あいまいさがなくなったので、すべての呼び出しがgetX()
同じ値を返します。
d.getX() = 42
d.A::getX() = 42
d.B::getX() = 42
d.C::getX() = 42
しかし、パラメータ化されたコンストラクタを呼び出したい場合はどうA
でしょうか?これは、次のコンストラクタから明示的に呼び出すことで実行できますD
。
D(int x, int y, int z): A(x), C(y), B(z)
通常、クラスは直接の親のコンストラクターのみを明示的に使用できますが、仮想継承の場合は除外されます。このルールを発見すると、「クリック」され、仮想インターフェースの理解に大いに役立ちました。
コードとclass B: virtual A
は、から継承されたすべてのクラスが自動的B
に作成A
するB
わけではないため、それ自体で作成する必要があることを意味します。
このステートメントを念頭に置くと、私が持っていたすべての質問に簡単に答えることができます。
- の
D
作成中、B
またはのC
パラメータの責任はありませんがA
、完全に最大D
です。
C
作成委譲しますA
にしD
ますが、B
独自のインスタンスが作成されますA
ので、ダイヤモンドの問題のバックを持参します
- 直接の子ではなく孫クラスで基本クラスのパラメーターを定義することは良い習慣ではないので、ひし形の問題が存在し、この対策が避けられない場合は許容する必要があります。