C++ のダイヤモンド継承における継承の曖昧さの問題
class A
{
public:
virtual void func1()
{
cout << "A::func1()" << endl;
}
int _a;
};
class B:virtual public A
{
public:
virtual void func1()
{
cout << "B::func1()" << endl;
}
int _b;
};
class C:virtual public A
{
public:
virtual void func1()
{
cout << "C::func1()" << endl;
}
int _c;
};
class D:public B,public C
{
public:
int _d;
};
int main(void)
{
D d;
return 0;
}
このコードはエラーを直接報告します。
なぜ?
B と C は両方とも A からの仮想継承であるため、主にデータの冗長性を解決するために使用されますが、これにより、D が作成されるときに A のインスタンスは 1 つだけになります。また、D は多重継承であり、B と C を継承するため、D ではそこには2 つの仮想関数テーブルがありますが、B と C の両方が A の func1 を書き換えているため、D が func1() を呼び出してどちらを呼び出すべきか分からない場合、最終的にあいまいさが生じます。
仮想継承により、サブクラスが基本クラスのインスタンスを共有できるようになり、サブクラス内に基本クラスのコピーが複数存在することが回避されます。
A の 3 つのクラス ABC、B、C の仮想継承がある場合、B と C がそれぞれ A の test() を書き換えた場合、このテストは書き換え後に共有されなくなりますが、他のテストは引き続き共有されます。
クラス B とクラス C がクラス A の仮想関数 test() をそれぞれ書き換えると、この関数は共有されなくなりますが、クラス B とクラス C の仮想関数テーブルのそれぞれの実装に存在します。このとき、クラスDでこの関数を呼び出す場合、必要に応じてクラスBとクラスCの実装をそれぞれ呼び出す必要がありますが、クラスAの実装を直接呼び出すことはできません。
仮想継承により、クラス B とクラス C がクラス A の他の仮想関数を含め、クラス A の同じインスタンスを共有するようになるため、他の仮想関数は引き続き共有されます。仮想継承は、派生クラス内の基本クラスのレイアウトと格納にのみ影響し、関数の書き換え動作には影響しません。関数の書き換え動作は、関数名とパラメーター リストの一致に基づいており、仮想継承とは関係ありません。したがって、クラス B とクラス C がクラス A 内の他の仮想関数をオーバーライドしない場合でも、それらの関数は共有されます。
class A
{
public:
virtual void func1()
{
cout << "A::func1()" << endl;
}
int _a;
};
class B:virtual public A
{
public:
virtual void func1()
{
cout << "B::func1()" << endl;
}
int _b;
};
class C:virtual public A
{
public:
virtual void func1()
{
cout << "C::func1()" << endl;
}
int _c;
};
class D:public B,public C
{
public:
virtual void func1()
{
cout << "D::func1()" << endl;
}
int _d;
};
正しいコードは次のようになりますが、ダイヤモンド継承の使用は一般的に推奨されておらず、特に面倒なので、できる限り使用しないでください。