7
#include <iostream>
using namespace std;

class A             { public: void eat(){ cout<<"A";} };
class B: public A   { public: void eat(){ cout<<"B";} };
class C: public A   { public: void eat(){ cout<<"C";} };
class D: public B,C { public: void eat(){ cout<<"D";} };

int main(){
    A *a = new D();
    a->eat();
}

これがダイヤモンド問題と呼ばれるかどうかはわかりませんが、なぜこれがうまくいかないのですか?

eat()forの定義を与えましたDBしたがって、やのコピーを使用する必要はありませんC(したがって、問題はないはずです)。

私が言ったとき、a->eat()(remembereat()は virtual ではないことを思い出してください)、eat()呼び出すことができるのは の 1 つだけですA

では、なぜこのエラーが発生するのでしょうか。

「A」は「D」のあいまいなベースです


A *a = new D();コンパイラにとって正確にはどういう意味ですか??

を使用すると同じ問題が発生しないのはなぜD *d = new D();ですか?

4

6 に答える 6

6

ひし形の結果、D オブジェクトで A の 2 つのインスタンスが発生し、どちらを参照しているかがあいまいです。これを解決するには、仮想継承を使用する必要があります。

class B: virtual public A   { public: void eat(){ cout<<"B";} };
class C: virtual public A   { public: void eat(){ cout<<"C";} };

実際に 1 つのインスタンスのみが必要であると仮定します。また、あなたが本当に意味していたと思います:

class D: public B, public C { public: void eat(){ cout<<"D";} };
于 2010-04-17T13:54:02.067 に答える
2

コンパイル エラーが "A *a = new D();" にあることに注意してください。「食べる」という電話ではありません。

問題は、非仮想継承を使用したため、最終的にクラス A が 2 回 (B を介して 1 回、C を介して 1 回) になることです。たとえば、メンバー m を A に追加すると、D にはそれらのうちの 2 つが含まれます: B:: m、および C::m。

派生グラフで A を 2 回使用したい場合があります。その場合、どの A について話しているのかを常に示す必要があります。D では、B::m と C::m を別々に参照できます。

ただし、本当に 1 つの A だけが必要な場合は、仮想継承を使用する必要があります。

于 2010-04-17T13:59:57.973 に答える
2

少し異なるシナリオを想像してください

class A             { protected: int a; public: void eat(){ a++; cout<<a;} };
class B: public A   { public: void eat(){ cout<<a;} };
class C: public A   { public: void eat(){ cout<<a;} };
class D: public B,C { public: void eat(){ cout<<"D";} };

int main(){
    A *a = new D();
    a->eat();
}

これが機能する場合、ainBまたはainをインクリメントしCますか? だからこそ、曖昧です。ポインターと任意のthis非静的データ メンバーは、2 つのAサブオブジェクト (一方はBサブオブジェクトに含まれ、もう 1 つはサブオブジェクトに含まれる) で区別されCます。このようにコードを変更してみてください (コンパイルして "A" を出力するという点で) 動作します。

class A             { public: void eat(){ cout<<"A";} };
class B: public A   { public: void eat(){ cout<<"B";} };
class C: public A   { public: void eat(){ cout<<"C";} };
class D: public B, public C { public: void eat(){ cout<<"D";} };

int main(){
    A *a = static_cast<B*>(new D());
      // A *a = static_cast<C*>(new D());
    a->eat();
}

これは、それぞれeatAサブオブジェクトとサブオブジェクトを呼び出します。BC

于 2010-04-17T14:04:20.783 に答える
2

本当に珍しい状況では、ニールの答えは実際には間違っています (少なくとも部分的に)。

仮想継承を使用しないと、最終的なオブジェクトで の 2 つの個別のコピーが取得されますA

「ひし形」Aは、最終的なオブジェクトでの単一のコピーになり、仮想継承を使用して生成されます。

代替テキスト

「ひし形」は、最終的なオブジェクトに のコピーが1 つしかないことを意味するため、 への参照があいまいさを生むことはありません。仮想継承がなければ、参照は 2 つの異なるオブジェクト (図の左側または右側) のいずれかを参照する可能性があります。AAA

于 2010-04-17T14:13:03.377 に答える
0

あなたが望む:(仮想継承で達成可能)

  D
  / \
B C
  \ /
  A

そうではありません:(仮想継承なしで何が起こるか)

    D
   / \
  B C
  | | |
  あ

仮想継承とは、基本Aクラスのインスタンスが 2 つではなく 1 つだけになることを意味します。

あなたの型Dには 2 つの vtable ポインター (最初の図で確認できます) があり、1つは仮想継承者用Bで、もう 1 つは継承者用です。 のオブジェクト サイズは、現在 2 つのポインタを格納しているため増加しています。ただし、現在は 1 つしかありません。 CADA

B::Aとは同じなのでC::A、 からのあいまいな呼び出しはありませんD。仮想継承を使用しない場合は、上の 2 番目の図になります。そして、 A のメンバーへの呼び出しはあいまいになり、どのパスを使用するかを指定する必要があります。

ウィキペディアには別の良い概要と例があります ここに

于 2010-04-17T17:12:14.703 に答える
0

あなたが得ているエラーは呼び出しから来ているのではありませんeat()- それは前の行から来ています。あいまいさを生み出すのはアップキャストそのものです。Neil Butterworth が指摘しているように、 には の 2 つのコピーがAありD、コンパイラはどちらを指し示すかを認識していませんa

于 2010-04-17T13:57:13.360 に答える