Warum noch eine Antwort?
Nun, viele Posts auf SO und Artikel außerhalb sagen, dass das Diamantproblem gelöst wird, indem eine einzelne Instanz Aanstelle von zwei erstellt wird (eine für jedes Elternteil von D), wodurch Mehrdeutigkeiten aufgelöst werden. Dies gab mir jedoch kein umfassendes Verständnis des Prozesses, und ich bekam noch mehr Fragen wie
- Was ist, wenn
Bund Cversucht, verschiedene Instanzen von Az. B. dem Aufrufen eines parametrisierten Konstruktors mit verschiedenen Parametern ( D::D(int x, int y): C(x), B(y) {}) zu erstellen ? Welche Instanz von Awird ausgewählt, um Teil davon zu werden D?
- Was ist, wenn ich nicht-virtuelle Vererbung für
B, aber virtuelle Vererbung für verwende C? Reicht es aus, eine einzelne Instanz von Ain zu erstellen D?
- Sollte ich von nun an standardmäßig immer die virtuelle Vererbung als vorbeugende Maßnahme verwenden, da sie mögliche Diamantprobleme mit geringen Leistungskosten und ohne weitere Nachteile löst?
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:
#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;
}
Gehen wir die Ausgabe durch. Das Ausführen B b(2);erstellt A(2)wie erwartet, dasselbe 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 beide Bund C, jeder von ihnen schafft seine eigenen A, so haben wir doppelt Ain d:
Create d(2,3):
A::A(2) C::C(2) A::A(3) B::B(3) D::D(2, 3)
Dies ist der Grund für d.getX()den Kompilierungsfehler, da der Compiler nicht auswählen kann, für welche AInstanz er die Methode aufrufen soll. Es ist jedoch möglich, Methoden direkt für die ausgewählte übergeordnete Klasse aufzurufen:
d.B::getX() = 3
d.C::getX() = 2
Virtualität
Fügen wir nun die virtuelle Vererbung hinzu. 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)
Sie können sehen, Awird mit dem Standardkonstruktor erstellt, der die von den Konstruktoren von Bund übergebenen Parameter ignoriert C. Da die Mehrdeutigkeit weg ist, geben alle Aufrufe getX()denselben Wert zurück:
d.getX() = 42
d.A::getX() = 42
d.B::getX() = 42
d.C::getX() = 42
Aber was ist, wenn wir einen parametrisierten Konstruktor aufrufen wollen A? Dies kann durch expliziten Aufruf vom Konstruktor von D:
D(int x, int y, int z): A(x), C(y), B(z)
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 Abedeutet, dass jede Klasse, von der geerbt wurde, Bjetzt selbst erstellt Awerden muss, da dies Bnicht automatisch erfolgt.
Mit dieser Aussage ist es einfach, alle Fragen zu beantworten, die ich hatte:
- Während der
DErstellung ist weder für Bnoch Cverantwortlich für Parameter von A, es liegt ganz bei Dnur.
Cwird die Erstellung von Aan delegieren D, aber Beine eigene Instanz erstellen A, um das Diamantproblem zurückzubringen
- Das Definieren von Basisklassenparametern in der Enkelklasse anstelle eines direkten Kindes ist keine gute Vorgehensweise. Daher sollte dies toleriert werden, wenn ein Diamantproblem vorliegt und diese Maßnahme unvermeidbar ist.