Was ist das Diamantproblem in C++? Wie man es erkennt und wie man es repariert
Mehrfachvererbung in C++ ist mächtig, aber ein kniffliges Werkzeug, das oft zu Problemen führt, wenn es nicht sorgfältig verwendet wird – Probleme wie das Diamantproblem.
In diesem Artikel werden wir das Diamantproblem besprechen, wie es durch Mehrfachvererbung entsteht und was Sie tun können, um das Problem zu lösen.
Mehrfachvererbung in C++
Mehrfachvererbung ist eine Funktion der objektorientierten Programmierung (OOP), bei der eine Unterklasse von mehr als einer Oberklasse erben kann. Mit anderen Worten, eine untergeordnete Klasse kann mehr als ein Elternteil haben.
Die folgende Abbildung zeigt eine bildliche Darstellung von Mehrfachvererbungen.
Im obigen Diagramm hat Klasse C Klasse A und Klasse B als Eltern.
Betrachten wir ein reales Szenario, erbt ein Kind von seinem Vater und seiner Mutter. Ein Kind kann also als abgeleitete Klasse mit „Vater“ und „Mutter“ als Eltern dargestellt werden. In ähnlicher Weise können wir viele solcher realen Beispiele für Mehrfachvererbung haben.
Bei der Mehrfachvererbung werden die Konstruktoren einer geerbten Klasse in der Reihenfolge ihrer Vererbung ausgeführt. Auf der anderen Seite werden Destruktoren in umgekehrter Reihenfolge ihrer Vererbung ausgeführt.
Lassen Sie uns nun die Mehrfachvererbung veranschaulichen und die Reihenfolge der Konstruktion und Zerstörung von Objekten überprüfen.
Code-Illustration der Mehrfachvererbung
Für die Abbildung der Mehrfachvererbung haben wir die obige Darstellung exakt in C++ programmiert. Der Code für das Programm ist unten angegeben.
#include<iostream>
using namespace std;
class A //base class A with constructor and destructor
{
public:
A() { cout << "class A::Constructor" << endl; }
~A() { cout << "class A::Destructor" << endl; }
};
class B //base class B with constructor and destructor
{
public:
B() { cout << "class B::Constructor" << endl; }
~B() { cout << "class B::Destructor" << endl; }
};
class C: public B, public A //derived class C inherits class A and then class B (note the order)
{
public:
C() { cout << "class C::Constructor" << endl; }
~C() { cout << "class C::Destructor" << endl; }
};
int main(){
C c;
return 0;
}
Die Ausgabe, die wir aus dem obigen Programm erhalten, ist wie folgt:
class B::Constructor
class A::Constructor
class C::Constructor
class C::Destructor
class A::Destructor
class B::Destructor
Wenn wir nun die Ausgabe überprüfen, sehen wir, dass die Konstruktoren in der Reihenfolge B, A und C aufgerufen werden, während die Destruktoren in umgekehrter Reihenfolge sind. Nachdem wir nun die Grundlagen der Mehrfachvererbung kennen, besprechen wir das Diamantproblem.
Das Diamantproblem, erklärt
Das Diamantproblem tritt auf, wenn eine untergeordnete Klasse von zwei Elternklassen erbt, die beide eine gemeinsame Großelternklasse teilen. Dies ist im folgenden Diagramm dargestellt:
Hier haben wir eine Klasse Kind, die von den Klassen Vater und Mutter erbt. Diese beiden Klassen wiederum erben die Klasse Person, da sowohl Vater als auch Mutter Person sind.
Wie in der Abbildung gezeigt, erbt die Klasse Kind die Merkmale der Klasse Person zweimal – einmal vom Vater und erneut von der Mutter. Dies führt zu Mehrdeutigkeiten, da der Compiler nicht versteht, welchen Weg er einschlagen soll.
Dieses Szenario führt zu einem rautenförmigen Vererbungsgraphen und wird bekanntlich als "Das Diamantproblem" bezeichnet.
Code-Illustration des Diamantproblems
Unten haben wir das obige Beispiel einer rautenförmigen Vererbung programmatisch dargestellt. Der Code ist unten angegeben:
#include<iostream>
using namespace std;
class Person { //class Person
public:
Person(int x) { cout << "Person::Person(int) called" << endl; }
};
class Father : public Person { //class Father inherits Person
public:
Father(int x):Person(x) {
cout << "Father::Father(int) called" << endl;
}
};
class Mother : public Person { //class Mother inherits Person
public:
Mother(int x):Person(x) {
cout << "Mother::Mother(int) called" << endl;
}
};
class Child : public Father, public Mother { //Child inherits Father and Mother
public:
Child(int x):Mother(x), Father(x) {
cout << "Child::Child(int) called" << endl;
}
};
int main() {
Child child(30);
}
Nachfolgend die Ausgabe dieses Programms:
Person::Person(int) called
Father::Father(int) called
Person::Person(int) called
Mother::Mother(int) called
Child::Child(int) called
Jetzt können Sie die Mehrdeutigkeit hier sehen. Der Konstruktor der Person-Klasse wird zweimal aufgerufen: einmal beim Erstellen des Vater-Klassenobjekts und dann beim Erstellen des Mutter-Klassenobjekts. Die Eigenschaften der Person-Klasse werden zweimal vererbt, was zu Mehrdeutigkeiten führt.
Da der Konstruktor der Klasse Person zweimal aufgerufen wird, wird der Destruktor auch zweimal aufgerufen, wenn das Objekt der Klasse Child zerstört wird.
Wenn Sie das Problem nun richtig verstanden haben, besprechen wir nun die Lösung des Diamantproblems.
So beheben Sie das Diamantproblem in C++
Die Lösung des Diamantproblems besteht darin, das virtuelle Schlüsselwort zu verwenden. Wir machen die beiden Elternklassen (die von derselben Großelternklasse erben) zu virtuellen Klassen, um zwei Kopien der Großelternklasse in der Kindklasse zu vermeiden.
Lassen Sie uns die obige Abbildung ändern und die Ausgabe überprüfen:
Code-Illustration zur Behebung des Diamantproblems
#include<iostream>
using namespace std;
class Person { //class Person
public:
Person() { cout << "Person::Person() called" << endl; } //Base constructor
Person(int x) { cout << "Person::Person(int) called" << endl; }
};
class Father : virtual public Person { //class Father inherits Person
public:
Father(int x):Person(x) {
cout << "Father::Father(int) called" << endl;
}
};
class Mother : virtual public Person { //class Mother inherits Person
public:
Mother(int x):Person(x) {
cout << "Mother::Mother(int) called" << endl;
}
};
class Child : public Father, public Mother { //class Child inherits Father and Mother
public:
Child(int x):Mother(x), Father(x) {
cout << "Child::Child(int) called" << endl;
}
};
int main() {
Child child(30);
}
Hier haben wir das Schlüsselwort virtual verwendet, wenn die Klassen Vater und Mutter die Klasse Person erben. Dies wird normalerweise als "virtuelle Vererbung" bezeichnet, die garantiert, dass nur eine einzige Instanz der geerbten Klasse (in diesem Fall der Klasse Person) weitergegeben wird.
Mit anderen Worten, die Child-Klasse hat eine einzelne Instanz der Person-Klasse, die sowohl von der Father- als auch der Mother-Klasse geteilt wird. Durch eine einzelne Instanz der Person-Klasse wird die Mehrdeutigkeit aufgelöst.
Die Ausgabe des obigen Codes ist unten angegeben:
Person::Person() called
Father::Father(int) called
Mother::Mother(int) called
Child::Child(int) called
Hier sehen Sie, dass der Konstruktor der Klasse Person nur einmal aufgerufen wird.
Bei der virtuellen Vererbung ist zu beachten, dass selbst dann, wenn der parametrisierte Konstruktor der Person-Klasse explizit von den Konstruktoren der Vater- und der Mutter-Klasse über Initialisierungslisten aufgerufen wird, nur der Basiskonstruktor der Person-Klasse aufgerufen wird .
Dies liegt daran, dass es nur eine einzige Instanz einer virtuellen Basisklasse gibt, die von mehreren Klassen geteilt wird, die davon erben.
Um zu verhindern, dass der Basiskonstruktor mehrmals ausgeführt wird, wird der Konstruktor für eine virtuelle Basisklasse nicht von der Klasse aufgerufen, die davon erbt. Stattdessen wird der Konstruktor vom Konstruktor der konkreten Klasse aufgerufen.
Im obigen Beispiel ruft die Klasse Child direkt den Basiskonstruktor für die Klasse Person auf.
Was ist, wenn Sie den parametrisierten Konstruktor der Basisklasse ausführen müssen? Sie können dies tun, indem Sie es explizit in der Child-Klasse statt in der Father- oder Mother-Klasse aufrufen.
Das Diamantproblem in C++, gelöst
Das Diamantproblem ist eine Mehrdeutigkeit, die bei Mehrfachvererbung auftritt, wenn zwei Elternklassen von derselben Großelternklasse erben und beide Elternklassen von einer einzigen Kindklasse geerbt werden. Ohne virtuelle Vererbung würde die Kindklasse die Eigenschaften der Großelternklasse zweimal erben, was zu Mehrdeutigkeiten führt.
Dies kann in realem Code häufig auftauchen, daher ist es wichtig, diese Mehrdeutigkeit zu beheben, wenn sie entdeckt wird.
Das Diamantproblem wird durch die virtuelle Vererbung behoben, bei der das Schlüsselwort virtual verwendet wird, wenn Elternklassen von einer gemeinsamen Großelternklasse erben. Dadurch wird nur eine Kopie der Großelternklasse erstellt, und die Objektkonstruktion der Großelternklasse wird von der Kindklasse durchgeführt.