2回継承
二重継承では、あいまいさが生じます。コンパイラは、2 つの A ベースのどちらを使用するかを認識できません。2 つの A ベースが必要な場合 (場合によってはこれが必要な場合もあります)、B または C にキャストすることでそれらを選択できます。ここでのデフォルト キャストから最も適切なのはstatic_cast
(利用可能な最も弱いものとして) ですが、そうではありません。派生型にキャストしていないため、本当に必要です(ケースのニーズよりもまだ強力です)。カスタムsafe_cast
テンプレートは次のように機能します。
/// cast using implicit conversions only
template <class To,class From>
inline To safe_cast( const From &from ) {return from;}
main()
{
D obj;
foo(safe_cast<B *>(&obj)); //error. How do i call with D's B part.
}
コンパイル時の型 - テンプレートを使用
また、仮想継承の場合、オフセット情報を vtable に格納する必要がある理由。これは、コンパイル時に決定できます。上記の場合、D のオブジェクトで foo を渡すと、コンパイル時にのみ D の A 部分のオフセットを計算できます。
これは誤解です。現在記述されている foo 関数には、B * または C* を渡したとしても、A * 以外の ptr 型に関するコンパイル型情報はありません。コンパイル時に渡された型に基づいて foo が動作できるようにする場合は、テンプレートを使用する必要があります。
template <class TypeDerivedFromA>
int foo(TypeDerivedFromA *ptr)
{
ptr->eat();
}
仮想継承
あなたの質問は仮想継承に言及しています。仮想継承を使用する場合は、次のように指定する必要があります。
class B: public virtual A ...
class C: public virtual A ...
これでコードはコンパイルされますが、このソリューションでは B::A または C::A を選択する方法がありません (A は 1 つしかありません)。
仮想機能
さらに、あなたの質問は、2 つの異なる概念、仮想継承 (2 つの中間基本クラス間で 1 つの基本クラスを共有することを意味します) と仮想関数 (基本クラス ポインターを介して派生クラス関数を呼び出すことができることを意味します) を混同しているようです)。B::eat を A ポインターを使用して呼び出す場合は、仮想関数を使用して、仮想継承を使用せずにこれを行うことができます (実際には、上記で説明したように、仮想継承によりそうすることができなくなります)。
class A
{
int a;
int b;
public:
virtual void eat()
{
cout<<"A::eat()"<<endl;
}
};
仮想関数が受け入れられない場合、上記で説明したように、これに対するコンパイル時のメカニズムはテンプレートです。