
Möb
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
kann einem Zeiger vom Typ zugewiesen werden A
aber es hat zwei Wege, denen es folgen kann, die es aber nicht selbst entscheiden kann.
Wie löst also die virtuelle Vererbung das Problem (hilft dem Compiler, die Entscheidung zu treffen)?

Brian R. Bondy
Sie wollen: (Erreichbar mit virtueller Vererbung)
A
/ \
B C
\ /
D
Und nicht: (Was passiert ohne virtuelle Vererbung)
A A
| |
B C
\ /
D
Virtuelle Vererbung bedeutet, dass es nur eine Instanz der Basis gibt A
Klasse nicht 2.
Dein Typ D
hätte 2 vtable-Zeiger (Sie können sie im ersten Diagramm sehen), einen für B
und eine für C
die quasi erben A
. D
Die Objektgröße von wurde erhöht, weil es jetzt 2 Zeiger speichert; jedoch gibt es nur einen A
jetzt.
Damit B::A
und C::A
sind gleich und so kann es zu keinen mehrdeutigen Aufrufen kommen D
. Wenn Sie keine virtuelle Vererbung verwenden, haben Sie das zweite Diagramm oben. Und jeder Aufruf an ein Mitglied von A wird dann mehrdeutig und Sie müssen angeben, welchen Weg Sie einschlagen möchten.
Wikipedia hat hier einen weiteren guten Überblick und ein Beispiel
Warum noch eine Antwort?
Nun, viele Posts auf SO und Artikel außerhalb sagen, dass das Diamond-Problem gelöst wird, indem eine einzelne Instanz von erstellt wird A
statt zwei (eins für jeden Elternteil von D
), wodurch Mehrdeutigkeiten beseitigt werden. Dies gab mir jedoch kein umfassendes Verständnis des Prozesses, ich endete mit noch mehr Fragen wie
- was, wenn
B
und C
versucht, verschiedene Instanzen von zu erstellen A
zB Aufruf parametrisierter Konstruktoren mit unterschiedlichen Parametern (D::D(int x, int y): C(x), B(y) {}
)? Welche Instanz von A
ausgewählt werden, um Teil davon zu werden D
?
- Was ist, wenn ich die nicht-virtuelle Vererbung verwende?
B
sondern virtuell für C
? Reicht es aus, um eine einzelne Instanz von zu erstellen A
in D
?
- sollte ich die virtuelle Vererbung von nun an standardmäßig als vorbeugende Maßnahme verwenden, da sie ein mögliches Diamantproblem mit geringen Leistungskosten und ohne andere Nachteile löst?
Verhalten nicht vorhersagen zu können, ohne Codebeispiele auszuprobieren, bedeutet, das Konzept nicht zu verstehen. Unten ist, was mir geholfen hat, mich um die virtuelle Vererbung zu kümmern.
Doppel a
Beginnen wir zunächst mit diesem Code ohne virtuelle Vererbung:
#include<iostream>
using namespace std;
class A {
public:
A() { cout << "A::A() "; }
A(int x) : m_x(x) { cout << "A::A(" << x << ") "; }
int getX() const { return m_x; }
private:
int m_x = 42;
};
class B : public A {
public:
B(int x):A(x) { cout << "B::B(" << x << ") "; }
};
class C : public A {
public:
C(int x):A(x) { cout << "C::C(" << x << ") "; }
};
class D : public C, public B {
public:
D(int x, int y): C(x), B(y) {
cout << "D::D(" << x << ", " << y << ") "; }
};
int main() {
cout << "Create b(2): " << endl;
B b(2); cout << endl << endl;
cout << "Create c(3): " << endl;
C c(3); cout << endl << endl;
cout << "Create d(2,3): " << endl;
D d(2, 3); cout << endl << endl;
// error: request for member 'getX' is ambiguous
//cout << "d.getX() = " << d.getX() << endl;
// error: 'A' is an ambiguous base of 'D'
//cout << "d.A::getX() = " << d.A::getX() << endl;
cout << "d.B::getX() = " << d.B::getX() << endl;
cout << "d.C::getX() = " << d.C::getX() << endl;
}
Lassen Sie uns die Ausgabe durchgehen. Ausführen B b(2);
schafft A(2)
wie erwartet, das gleiche für C c(3);
:
Create b(2):
A::A(2) B::B(2)
Create c(3):
A::A(3) C::C(3)
D d(2, 3);
braucht beides B
und C
jeder von ihnen erstellt seine eigenen A
also haben wir doppelt A
in d
:
Create d(2,3):
A::A(2) C::C(2) A::A(3) B::B(3) D::D(2, 3)
Das ist der Grund für d.getX()
um einen Kompilierungsfehler zu verursachen, da der Compiler nicht auswählen kann, welcher A
Instanz, für die die Methode aufgerufen werden soll. Dennoch ist es möglich, Methoden direkt für die ausgewählte Elternklasse aufzurufen:
d.B::getX() = 3
d.C::getX() = 2
Virtualität
Lassen Sie uns nun die virtuelle Vererbung hinzufügen. Verwenden des gleichen Codebeispiels mit den folgenden Änderungen:
class B : virtual public A
...
class C : virtual public A
...
cout << "d.getX() = " << d.getX() << endl; //uncommented
cout << "d.A::getX() = " << d.A::getX() << endl; //uncommented
...
Springen wir zur Erstellung von d
:
Create d(2,3):
A::A() C::C(2) B::B(3) D::D(2, 3)
Du kannst sehen, A
wird mit dem Standardkonstruktor erstellt, wobei Parameter ignoriert werden, die von Konstruktoren von übergeben werden B
und C
. Da die Mehrdeutigkeit weg ist, werden alle Anrufe an getX()
denselben Wert zurückgeben:
d.getX() = 42
d.A::getX() = 42
d.B::getX() = 42
d.C::getX() = 42
Aber was ist, wenn wir den parametrisierten Konstruktor for aufrufen möchten? A
? Dies kann durch expliziten Aufruf vom Konstruktor von erfolgen D
:
D(int x, int y, int z): A(x), C(y), B(z)
Normalerweise kann die Klasse explizit nur Konstruktoren direkter Eltern verwenden, aber es gibt einen Ausschluss für den Fall der virtuellen Vererbung. Die Entdeckung dieser Regel hat für mich “geklickt” und sehr geholfen, virtuelle Schnittstellen zu verstehen:
Code class B: virtual A
bedeutet, dass jede Klasse geerbt wird B
ist jetzt für die Erstellung verantwortlich A
von selbst, da B
wird es nicht automatisch tun.
Mit dieser Aussage im Hinterkopf ist es einfach, alle Fragen zu beantworten, die ich hatte:
- Während
D
Schöpfung auch nicht B
Noch C
ist verantwortlich für die Parameter von A
es ist ganz bis zu D
nur.
C
wird die Erstellung von delegieren A
zu D
aber B
erstellt eine eigene Instanz von A
wodurch das Diamantproblem zurückgebracht wird
- Das Definieren von Basisklassenparametern in der Enkelklasse statt in der direkten Kindklasse ist keine gute Praxis, daher sollte es toleriert werden, wenn ein Rautenproblem besteht und diese Maßnahme unvermeidlich ist.

el.pescado – нет войне
Instanzen abgeleiteter Klassen speichern die Mitglieder ihrer Grundklassen.
Ohne virtuelle Vererbung, die Speicherlayouts aussehen (beachten Sie die zwei Kopien der A
Mitglieder in der Klasse D
):
class A: [A members]
class B: public A [A members|B members]
class C: public A [A members|C members]
class D: public B, public C [A members|B members|A members|C members|D members]
Mit virtueller Vererbung die Speicherlayouts aussehen (beachten Sie die Einzel Kopie der A
Mitglieder in der Klasse D
):
class A: [A members]
class B: virtual public A [B members|A members]
| ^
v |
virtual table B
class C: virtual public A [C members|A members]
| ^
v |
virtual table C
class D: public B, public C [B members|C members|D members|A members]
| | ^
v v |
virtual table D ----------------|
Für jede abgeleitete Klasse erstellt der Compiler eine virtuelle Tabelle, die Zeiger auf die Mitglieder ihrer virtuellen Basisklassen enthält, die in der abgeleiteten Klasse gespeichert sind, und fügt einen Zeiger zu dieser virtuellen Tabelle in der abgeleiteten Klasse hinzu.

Ameise
Das Problem ist nicht die Weg der Compiler muss folgen. Das Problem ist die Endpunkt dieses Weges: das Ergebnis der Besetzung. Bei Typkonvertierungen spielt der Pfad keine Rolle, sondern nur das Endergebnis.
Wenn Sie die normale Vererbung verwenden, hat jeder Pfad seinen eigenen unverwechselbaren Endpunkt, was bedeutet, dass das Ergebnis der Umwandlung mehrdeutig ist, was das Problem ist.
Wenn Sie virtuelle Vererbung verwenden, erhalten Sie eine rautenförmige Hierarchie: Beide Pfade führen zum selben Endpunkt. In diesem Fall besteht das Problem der Pfadwahl nicht mehr (genauer gesagt spielt es keine Rolle mehr), da beide Pfade zum gleichen Ergebnis führen. Das Ergebnis ist nicht mehr eindeutig – darauf kommt es an. Der genaue Pfad nicht.
Eigentlich sollte das Beispiel wie folgt aussehen:
#include <iostream>
//THE DIAMOND PROBLEM SOLVED!!!
class A { public: virtual ~A(){ } virtual void eat(){ std::cout<<"EAT=>A";} };
class B: virtual public A { public: virtual ~B(){ } virtual void eat(){ std::cout<<"EAT=>B";} };
class C: virtual public A { public: virtual ~C(){ } virtual void eat(){ std::cout<<"EAT=>C";} };
class D: public B,C { public: virtual ~D(){ } virtual void eat(){ std::cout<<"EAT=>D";} };
int main(int argc, char ** argv){
A *a = new D();
a->eat();
delete a;
}
… so 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 angeben, dass die Methoden virtuell sein sollen, damit die Methoden korrekt überschrieben werden …
9899400cookie-checkWie löst die virtuelle Vererbung die “Diamant”-Mehrdeutigkeit (Mehrfachvererbung)?yes