2010-04-17 13 views
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(); 
} 

Nie jestem pewien, czy to się nazywa problemem diamentowym, ale dlaczego to nie działa?Dlaczego nie ma dwuznaczności w tym wzorze diamentu?

Podałem definicję dla eat() dla D. Nie trzeba więc używać ani kopii B ani C (więc nie powinno być problemu).

Kiedy powiedziałem, a->eat() (pamiętaj eat() nie jest wirtualny), jest tylko jednym z możliwych eat() zadzwonić, że od A.

Dlaczego więc mogę dostać ten błąd:

'A' is an ambiguous base of 'D'


Co dokładnie ma na myśli A *a = new D(); do kompilatora ??

i

Dlaczego nie ten sam problem występuje podczas korzystania D *d = new D();?

+0

http://www.parashift.com/c++-faq-lite/multiple-inheritance.html#faq-25.9 – JRL

Odpowiedz

2

Wyobraźmy sobie nieco inny scenariusz

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(); 
} 

Jeśli to się uda, to zwiększa zawartość a w B lub a w C? Dlatego jest niejednoznaczny.this wskaźnik i nie będących statyczny element danych jest różna dla dwóch A podobiektów (z których jeden jest zawarty w B podobiektu, a drugi przez C podobiektu). Spróbuj zmienić swój kod tak i będzie działać (w który kompiluje i drukuje „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(); 
} 

Że wezwie eat na A podobiektu z B i C odpowiednio.

+0

@Johannes Schaub - litb: ok, rozumiem co powiedziałeś. Ale dlaczego ten sam problem nie występuje, gdy używam 'D * d = new D();' – Moeb

+1

@cambr, wyszukiwanie nazw zatrzymuje się, gdy robisz 'd-> eat()' w zakresie 'D', ponieważ znajduje 'jeść' w' D'. Nie dotknie "jedz" w 'A',' B' lub 'C' i nie będzie próbował ich wywoływać. Tak więc w tym przypadku nie ma konwersji na "A", a zatem nie powstaje żadna niejednoznaczność. –

+0

Gdybyś spróbował 'd-> A :: eat()', próbując wywołać 'eat' na' A', znowu miałbyś ten sam problem, ponieważ spróbuje przekształcić 'D *' na 'A ' * '. –

6

Wyniki diament w dwóch instancji w obiekcie D, a to nie jest jednoznaczne, który z nich odnoszą się do - trzeba użyć wirtualnego dziedziczenie aby rozwiązać ten problem:

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

zakładając, że rzeczywiście tylko chciał jeden egzemplarz. Zakładam również naprawdę oznaczało:

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

@Neil Butterworth: "kopie' A' w końcowej przedmiotu" Dlaczego czy skopiowałbym A w końcowym obiekcie? – Moeb

+0

@cambr używałem „kopia” w znaczeniu „wystąpienie” - Zmieniłem go. –

+0

@Neil Butterworth: Również zdefiniowałem 'eat()' jawnie w D. Więc nie trzeba używać kopii/instancji z 'B' lub' C'. – Moeb

0

Błąd dostajesz nie pochodzi wywołanie eat() - to pochodzący z linii wcześniej. To właśnie upcast tworzy niejednoznaczność. Jak wskazuje Neil Butterworth, w twoim D są dostępne dwie kopie A, a kompilator nie wie, który z nich ma być oznaczony jako a.

+0

@Mike: dlaczego mam 'kopie A w twoim D'. Zdefiniowałem oddzielne 'jeść()' dla D. – Moeb

+0

Nie ma to nic wspólnego z 'eat()'; wywołanie 'a-> eat()' samo w sobie nie obchodzi, jakie klasy wywodzą się z 'A' (jak mówisz,' eat' jest nie-wirtualny) - zawsze będzie wywoływać 'A :: eat'. Jak mówi @Neil, usuń wywołanie "jedz" i nadal masz ten sam problem. –

2

Należy zauważyć, że błąd kompilacji występuje w "A * a = new D();" linii, a nie na wezwanie do "jedzenia".

Problem polega na tym, że z powodu dziedziczenia innego niż wirtualne, kończy się to na klasie A dwa razy: raz przez B i raz przez C. Jeśli na przykład dodasz element m do A, to D ma dwa z nich : B :: m, i C :: m.

Czasami naprawdę chcesz mieć A dwukrotnie na wykresie wyprowadzenia, w którym to przypadku zawsze musisz wskazać, o którym mówisz. W D możesz odnieść się do B :: m i C :: m osobno.

Czasami naprawdę potrzebujesz tylko jednego A, w takim przypadku musisz użyć virtual inheritance.

2

Dla prawdziwie nietypowej sytuacji, odpowiedź Neil jest faktycznie źle (przynajmniej częściowo).

Z dziedziczeniem wirtualnym otrzymujesz dwie oddzielne kopie A w ostatecznym obiekcie.

„diament” Wyniki w jednym egzemplarzu z A w końcowej przedmiotu i jest produkowany przez użyciu wirtualny dziedziczenia:

alt text

Ponieważ „diament” oznacza nie tylko jeden kopia A w końcowej przedmiotu, odniesienie do A produkuje żadnych niejasności. Bez dziedziczenia wirtualnego odniesienie do A może odnosić się do jednego z dwóch różnych obiektów (ten po lewej lub ten po prawej na schemacie).

+0

Narysowałeś diagram obiektu - moja "zła" odpowiedź odnosiła się do wykresu dziedziczenia, który jest diamentem. –

+0

@Jerry Coffin: Dlaczego ten sam problem nie występuje, gdy używam 'D * d = new D();'? Również w tym przypadku tworzony jest obiekt typu 'D' i powinny pojawić się te same problemy. – Moeb

+0

@cambr: ponieważ 'A * a = new D();' nie może zdecydować, który sub-obiekt "A" ma wskazywać. Z 'D * d = new D();', wskaże obiekt 'D', a nie jeden z dwóch obiektów' A', więc nie ma wątpliwości co do tego, gdzie powinien wskazywać. –

0

Chcecie: (Osiągalna z wirtualnego dziedziczenia)

  D
 /\
B   C
  \ /
  A

A nie: (Co dzieje się bez wirtualnego dziedziczenia)

    D
  /  \
  B   C
  |     |
  A   A

Wirtualne dziedziczenie oznacza, że ​​nie będzie tylko 1 instancja klasy bazowej A nie 2.

Twój Typ D musiałby 2 vtable wskaźniki (można je zobaczyć w pierwszym rysunku), po jednym dla B i jeden dla C który praktycznie dziedziczą A. D Rozmiar obiektu został zwiększony, ponieważ przechowuje teraz 2 wskaźniki; jednak teraz jest tylko jeden A.

Tak więc B::A i C::A są takie same i dlatego nie może być żadnych niejednoznacznych połączeń od D. Jeśli nie używasz wirtualnego dziedziczenia, masz drugi diagram powyżej. Każde wezwanie do członka A staje się niejednoznaczne i musisz określić, którą ścieżkę chcesz podjąć.

Wikipedia has another good rundown and example here

Powiązane problemy