Warum gibt es Mehrdeutigkeit in diesem Rautenmuster?
-
27-09-2019 - |
Frage
#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();
}
Ich bin nicht sicher, dass diese Diamanten Problem genannt oder nicht, aber warum funktioniert das nicht?
Ich habe die defination für eat()
für D
gegeben. Von daher ist es auch nicht B
oder die C
Kopie verwenden muß (so sollte es kein Problem).
Als ich sagte, a->eat()
(erinnern eat()
nicht virtuell ist), gibt es nur eine mögliche eat()
zu Anruf ist, dass der A
.
Warum dann bekomme ich diesen Fehler:
'A' ist eine mehrdeutige Basis von 'D'
Was genau bedeutet A *a = new D();
Mittelwert an den Compiler ??
und
Warum das gleiche Problem nicht auf, wenn ich D *d = new D();
verwenden?
Lösung
Stellen Sie sich ein etwas anderes Szenario
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();
}
Wenn das funktionieren würde, würde es die a
in B
oder die a
in C
erhöhen? Deshalb ist es nicht eindeutig. Der this
Zeiger und jedes nicht-statisches Datenelement unterscheidet sich für die beiden A
Subobjekte (von denen einer durch den B
Subobjekt enthalten ist, und die andere von der C
Subobjekt). Versuchen Sie, Ihren Code wie folgt zu ändern und es wird funktionieren (dass es kompiliert und druckt „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();
}
Das wird jeweils eat
auf dem A
subobject von B
und C
nennen.
Andere Tipps
Die Diamant Ergebnisse in zwei Instanzen von A in der D-Objekt, und es ist nicht eindeutig, welche Sie beziehen sich auf - müssen Sie virtuelle Vererbung verwenden, um diese zu lösen:
class B: virtual public A { public: void eat(){ cout<<"B";} };
class C: virtual public A { public: void eat(){ cout<<"C";} };
unter der Annahme, dass Sie eigentlich nur eine Instanz wollen. Ich nehme an, Sie auch wirklich meinte:
class D: public B, public C { public: void eat(){ cout<<"D";} };
Beachten Sie, dass der Compiler-Fehler ist auf dem "A * a = new D ();" Linie, nicht auf den Anruf zu „essen“.
Das Problem ist, dass, weil Sie nicht-virtuelle Vererbung verwendet, um Sie mit der Klasse A zweimal am Ende: einmal durch B und einmal durch C. Wenn Sie zum Beispiel eines Mitglied m bis A hinzufügen, dann D zwei von ihnen haben: B :: m und C :: m.
Manchmal wollen Sie wirklich A zweimal in der Ableitung Graph haben, in dem Fall, dass Sie immer angeben müssen, welche A Sie sprechen. In D, würden Sie in der Lage sein, um Referenz B :: m und C :: m getrennt.
Manchmal, wenn Sie wirklich wollen, nur ein A, in diesem Fall müssen Sie zu verwenden virtuelle Vererbung .
Für eine wirklich ungewöhnliche Situation, Antwort Neil ist eigentlich falsch (zumindest teilweise).
Mit aus virtueller Vererbung, erhalten Sie zwei getrennte Kopien von A
in der letzten Aufgabe.
„Der Diamant“ Ergebnisse in einer einzigen Kopie der A
im endgültigen Objekt und erzeugt durch mit virtueller Vererbung:
Da die „Diamant“ bedeutet nur, es gibt eine Kopieren von A
im endgültigen Objekt erzeugt ein Verweis auf A
keine Zweideutigkeit. Ohne virtuelle Vererbung, ein Verweis auf A
zu einem von zwei verschiedenen Objekten (die auf der linken Seite oder auf der rechten Seite im Diagramm) beziehen könnte.
Der Fehler Sie bekommen kommt nicht von eat()
Aufruf - es ist von der Linie kommt vor. Es ist die upcast selbst, das die Mehrdeutigkeit erzeugt. Als Neil Butter weist darauf hin, gibt es zwei Kopien von A
in Ihrem D
, und der Compiler nicht weiß, welche Sie a
Punkt wollen bei.
Sie mögen: (Erreichbare mit virtueller Vererbung)
D
/ \
B C
\ /
A
Und nicht: (Was ohne virtuelle Vererbung geschieht)
D
/ \
B C
| |
A A
Virtuelle Vererbung bedeutet, dass es nur 1 Instanz der Basis A
Klasse nicht 2.
Ihre Art D
würde 2 VTable-Zeiger haben (Sie können sie im ersten Diagramm sehen), eine für B
und eine für C
, die praktisch erben A
. D
Objektgröße erhöht, da es 2 Zeiger speichert jetzt; jedoch gibt es nur eine A
jetzt.
So B::A
und C::A
gleich sind und so kann es keine zweideutigen Anrufe von D
sein. Wenn Sie nicht virtuelle Vererbung verwenden, müssen Sie das zweite Diagramm oben. Und jeder Anruf an ein Mitglied der A dann mehrdeutig wird, und Sie müssen angeben, welchen Weg Sie nehmen wollen.
Wikipedia hat einen weiteren guten Überblick und Beispiel hier