class A { public: void eat(){ cout<<"A";} };
class B: virtual public A { public: void eat(){ cout<<"B";} };
class C: virtual 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 verstehe das Diamantproblem, und der obige Code hat dieses Problem nicht.
Wie genau löst die virtuelle Vererbung das Problem?
Was ich verstehe:
Wenn ich sage A *a = new D();
, möchte der Compiler wissen, ob ein Objekt vom Typ D
einem Zeiger vom Typ zugewiesen werden kann A
, aber es hat zwei Pfade, denen er folgen kann, aber nicht selbst entscheiden kann.
Wie löst die virtuelle Vererbung das Problem (helfen Sie dem Compiler, die Entscheidung zu treffen)?
B
'oderC
' zu verwenden? Vielen Dank!Instanzen abgeleiteter Klassen "enthalten" Instanzen von Basisklassen, sodass sie im Speicher folgendermaßen aussehen:
Ohne virtuelle Vererbung würde die Instanz der Klasse D also wie folgt aussehen:
Beachten Sie also zwei "Kopien" von A-Daten. Virtuelle Vererbung bedeutet, dass innerhalb der abgeleiteten Klasse zur Laufzeit ein vtable-Zeiger festgelegt ist, der auf Daten der Basisklasse verweist, sodass Instanzen von B-, C- und D-Klassen folgendermaßen aussehen:
quelle
Warum noch eine Antwort?
Nun, viele Posts auf SO und Artikel außerhalb sagen, dass das Diamantproblem gelöst wird, indem eine einzelne Instanz
A
anstelle von zwei erstellt wird (eine für jedes Elternteil vonD
), wodurch Mehrdeutigkeiten aufgelöst werden. Dies gab mir jedoch kein umfassendes Verständnis des Prozesses, und ich bekam noch mehr Fragen wieB
undC
versucht, verschiedene Instanzen vonA
z. B. dem Aufrufen eines parametrisierten Konstruktors mit verschiedenen Parametern (D::D(int x, int y): C(x), B(y) {}
) zu erstellen ? Welche Instanz vonA
wird ausgewählt, um Teil davon zu werdenD
?B
, aber virtuelle Vererbung für verwendeC
? Reicht es aus, eine einzelne Instanz vonA
in zu erstellenD
?Das Verhalten nicht vorhersagen zu können, ohne Codebeispiele auszuprobieren, bedeutet, das Konzept nicht zu verstehen. Das Folgende hat mir geholfen, mich mit der virtuellen Vererbung zu befassen.
Doppel a
Beginnen wir zunächst mit diesem Code ohne virtuelle Vererbung:
Gehen wir die Ausgabe durch. Das Ausführen
B b(2);
erstelltA(2)
wie erwartet, dasselbe fürC c(3);
:D d(2, 3);
braucht beideB
undC
, jeder von ihnen schafft seine eigenenA
, so haben wir doppeltA
ind
:Dies ist der Grund für
d.getX()
den Kompilierungsfehler, da der Compiler nicht auswählen kann, für welcheA
Instanz er die Methode aufrufen soll. Es ist jedoch möglich, Methoden direkt für die ausgewählte übergeordnete Klasse aufzurufen:Virtualität
Fügen wir nun die virtuelle Vererbung hinzu. Verwenden des gleichen Codebeispiels mit den folgenden Änderungen:
Springen wir zur Erstellung von
d
:Sie können sehen,
A
wird mit dem Standardkonstruktor erstellt, der die von den Konstruktoren vonB
und übergebenen Parameter ignoriertC
. Da die Mehrdeutigkeit weg ist, geben alle AufrufegetX()
denselben Wert zurück:Aber was ist, wenn wir einen parametrisierten Konstruktor aufrufen wollen
A
? Dies kann durch expliziten Aufruf vom Konstruktor vonD
:Normalerweise kann die Klasse explizit nur Konstruktoren direkter Eltern verwenden, es gibt jedoch einen Ausschluss für den Fall der virtuellen Vererbung. Das Entdecken dieser Regel hat für mich "geklickt" und mir geholfen, virtuelle Schnittstellen zu verstehen:
Code
class B: virtual A
bedeutet, dass jede Klasse, von der geerbt wurde,B
jetzt selbst erstelltA
werden muss, da diesB
nicht automatisch erfolgt.Mit dieser Aussage ist es einfach, alle Fragen zu beantworten, die ich hatte:
D
Erstellung ist weder fürB
nochC
verantwortlich für Parameter vonA
, es liegt ganz beiD
nur.C
wird die Erstellung vonA
an delegierenD
, aberB
eine eigene Instanz erstellenA
, um das Diamantproblem zurückzubringenquelle
Das Problem ist nicht der Pfad, dem der Compiler folgen muss. Das Problem ist der Endpunkt dieses Pfades: das Ergebnis der Besetzung. Bei Typkonvertierungen spielt der Pfad keine Rolle, nur das Endergebnis.
Wenn Sie die normale Vererbung verwenden, hat jeder Pfad einen eigenen Endpunkt, was bedeutet, dass das Ergebnis der Umwandlung nicht eindeutig ist, was das Problem ist.
Wenn Sie die virtuelle Vererbung verwenden, erhalten Sie eine rautenförmige Hierarchie: Beide Pfade führen zum gleichen Endpunkt. In diesem Fall besteht das Problem der Pfadauswahl nicht mehr (oder genauer gesagt spielt es keine Rolle mehr), da beide Pfade zum gleichen Ergebnis führen. Das Ergebnis ist nicht mehr mehrdeutig - darauf kommt es an. Der genaue Pfad stimmt nicht.
quelle
Eigentlich sollte das Beispiel wie folgt sein:
... auf diese Weise wird die Ausgabe die richtige sein: "EAT => D"
Virtuelle Vererbung löst nur die Vervielfältigung des Großvaters! ABER Sie müssen immer noch die Methoden angeben, die virtuell sein sollen, damit die Methoden korrekt überschrieben werden ...
quelle