Meervoudige overerving in C++ is krachtig, maar een lastig hulpmiddel, dat vaak tot problemen leidt als het niet zorgvuldig wordt gebruikt - problemen zoals het diamantprobleem.

In dit artikel bespreken we het diamantprobleem, hoe het ontstaat door meervoudige overerving en wat u kunt doen om het probleem op te lossen.

Meerdere overerving in C++

Meervoudige overerving is een kenmerk van objectgeoriënteerd programmeren (OOP) waarbij een subklasse kan erven van meer dan één superklasse. Met andere woorden, een kindklasse kan meer dan één ouder hebben.

Onderstaande figuur toont een picturale weergave van meerdere erfenissen.

In het bovenstaande schema is klasse C heeft klasse A, eerste klasse en klasse B als zijn ouders.

Als we een realistisch scenario beschouwen, erft een kind van zijn vader en moeder. Een Kind kan dus worden weergegeven als een afgeleide klasse met “Vader” en “Moeder” als ouders. Evenzo kunnen we veel van dergelijke real-life voorbeelden van meervoudige overerving hebben.

Bij meervoudige overerving worden de constructors van een overgeërfde klasse uitgevoerd in de volgorde waarin ze worden geërfd. Aan de andere kant worden destructors uitgevoerd in de omgekeerde volgorde van hun erfenis.

Laten we nu de meervoudige overerving illustreren en de volgorde van constructie en vernietiging van objecten verifiëren.

Code-illustratie van meervoudige overerving

Voor de meervoudige overervingsillustratie hebben we de bovenstaande weergave exact geprogrammeerd in C++. De code voor het programma staat hieronder.

#erbij betrekken
namespace std; gebruiken;
klasse A // basisklasse A met constructor en destructor
{
openbaar:
A() { cout << "klasse A:: Constructor" << endl; }
~A() { cout << "klasse A:: Destructor" << endl; }
};
klasse B //basisklasse B met constructor en destructor
{
openbaar:
B() { cout << "klasse B:: Constructor" << endl; }
~B() { cout << "klasse B:: Destructor" << endl; }
};
klasse C: publiek B, publiek A // afgeleide klasse C erft klasse A en vervolgens klasse B (let op de volgorde)
{
openbaar:
C() { cout << "klasse C:: Constructor" << endl; }
~C() { cout << "klasse C:: Destructor" << endl; }
};
int hoofd(){
Cc;
retourneer 0;
}

De output die we verkrijgen van het bovenstaande programma is als volgt:

klasse B:: Constructeur
klasse A:: Constructeur
klasse C:: Constructor
klasse C:: Destructor
klasse A:: Destructor
klasse B:: Destructor

Als we nu de uitvoer controleren, zien we dat de constructors in volgorde B, A en C worden aangeroepen, terwijl de destructors in omgekeerde volgorde staan. Nu we de basis van meervoudige overerving kennen, gaan we verder met het bespreken van het diamantprobleem.

Het diamantprobleem, uitgelegd

Het diamantprobleem doet zich voor wanneer een kindklasse erft van twee bovenliggende klassen die beide een gemeenschappelijke grootouderklasse delen. Dit wordt geïllustreerd in onderstaand schema:

Hier, we hebben een klas Kind erven van klassen Vader en Moeder. Deze twee klassen erven op hun beurt de klasse Persoon omdat zowel Vader als Moeder Persoon zijn.

Zoals te zien is in de afbeelding, erft klasse Kind tweemaal de eigenschappen van klasse Persoon - een keer van Vader en nogmaals van Moeder. Dit geeft aanleiding tot dubbelzinnigheid omdat de compiler niet begrijpt welke kant hij op moet.

Dit scenario geeft aanleiding tot een ruitvormige overervingsgrafiek en wordt ook wel het "diamantprobleem" genoemd.

Code-illustratie van het diamantprobleem

Hieronder hebben we het bovenstaande voorbeeld van ruitvormige overerving programmatisch weergegeven. De code wordt hieronder gegeven:

#erbij betrekken
namespace std; gebruiken;
klasse Persoon { //klasse Persoon
openbaar:
Persoon (int x) { cout << "Persoon:: Persoon (int) genaamd" << endl; }
};
klasse Vader: openbaar Persoon { // klasse Vader erft Persoon
openbaar:
Vader (int x):Persoon (x) {
cout << "Vader:: Vader (int) gebeld" << endl;
}
};
klas Moeder: openbaar Persoon { // klas Moeder erft Persoon
openbaar:
Moeder (int x):Persoon (x) {
cout << "Moeder:: Moeder (int) gebeld" << endl;
}
};
klas Kind: publiek Vader, publiek Moeder { // Kind erft Vader en Moeder
openbaar:
Kind (int x):Moeder (x), Vader (x) {
cout << "Kind:: Kind (int) genoemd" << endl;
}
};
int hoofd() {
Kind kind (30);
}

Hieronder volgt de uitvoer van dit programma:

Persoon:: Persoon (int) genaamd
Vader:: Vader (int) gebeld
Persoon:: Persoon (int) genaamd
Moeder:: Moeder (int) gebeld
Kind:: Kind (int) genaamd

Nu zie je de dubbelzinnigheid hier. De klasseconstructor Person wordt twee keer aangeroepen: één keer wanneer het klasseobject Vader wordt gemaakt en vervolgens wanneer het klasseobject Moeder wordt gemaakt. De eigenschappen van de klasse Person worden twee keer overgeërfd, waardoor er onduidelijkheid ontstaat.

Aangezien de constructor van de klasse Person twee keer wordt aangeroepen, wordt de destructor ook twee keer aangeroepen wanneer het klasseobject Child wordt vernietigd.

Als je het probleem goed hebt begrepen, laten we dan de oplossing voor het diamantprobleem bespreken.

Hoe het diamantprobleem op te lossen in C++

De oplossing voor het diamantprobleem is het gebruik van de virtueel trefwoord. We maken van de twee ouderklassen (die erven van dezelfde grootouderklasse) virtuele klassen om twee kopieën van de grootouderklasse in de kindklasse te vermijden.

Laten we de bovenstaande afbeelding wijzigen en de uitvoer controleren:

Code-illustratie om het diamantprobleem op te lossen

#erbij betrekken
namespace std; gebruiken;
klasse Persoon { //klasse Persoon
openbaar:
Persoon() { cout << "Persoon:: Persoon() genaamd" << endl; } //Basisconstructor
Persoon (int x) { cout << "Persoon:: Persoon (int) genaamd" << endl; }
};
klas Vader: virtueel openbaar Persoon { // klas Vader erft Persoon
openbaar:
Vader (int x):Persoon (x) {
cout << "Vader:: Vader (int) gebeld" << endl;
}
};
klas Moeder: virtueel openbaar Persoon { // klas Moeder erft Persoon
openbaar:
Moeder (int x):Persoon (x) {
cout << "Moeder:: Moeder (int) gebeld" << endl;
}
};
klas Kind: publiek Vader, publiek Moeder { // klas Kind erft Vader en Moeder
openbaar:
Kind (int x):Moeder (x), Vader (x) {
cout << "Kind:: Kind (int) genoemd" << endl;
}
};
int hoofd() {
Kind kind (30);
}

Hier hebben we de gebruikt virtueel trefwoord wanneer klassen Vader en Moeder de klasse Persoon erven. Dit wordt meestal 'virtuele overerving' genoemd, wat garandeert dat slechts één exemplaar van de overgeërfde klasse (in dit geval de klasse Persoon) wordt doorgegeven.

Met andere woorden, de klasse Kind heeft één exemplaar van de klasse Persoon, die wordt gedeeld door zowel de klassen Vader als Moeder. Door een enkele instantie van de klasse Person te hebben, wordt de dubbelzinnigheid opgelost.

De uitvoer van de bovenstaande code wordt hieronder gegeven:

Persoon:: Persoon() genaamd
Vader:: Vader (int) gebeld
Moeder:: Moeder (int) gebeld
Kind:: Kind (int) genaamd

Hier kun je zien dat de constructor van de klasse Person maar één keer wordt aangeroepen.

Een ding om op te merken over virtuele overerving is dat zelfs als de geparametriseerde constructor van de Persoonsklasse wordt expliciet aangeroepen door constructeurs van vader- en moederklassen door middel van initialisatie lijsten, alleen de basisconstructor van de klasse Person wordt aangeroepen.

Dit komt omdat er slechts één exemplaar is van een virtuele basisklasse die wordt gedeeld door meerdere klassen die ervan overerven.

Om te voorkomen dat de basisconstructor meerdere keren wordt uitgevoerd, wordt de constructor voor een virtuele basisklasse niet aangeroepen door de klasse die ervan overerft. In plaats daarvan wordt de constructor aangeroepen door de constructor van de concrete klasse.

In het bovenstaande voorbeeld roept de klasse Kind rechtstreeks de basisconstructor voor de klasse Persoon aan.

Verwant: Een beginnershandleiding voor de standaardsjabloonbibliotheek in C++

Wat als u de geparametriseerde constructor van de basisklasse moet uitvoeren? U kunt dit doen door het expliciet in de klas Kind te noemen in plaats van in de klas Vader of Moeder.

Het diamantprobleem in C++, opgelost

Het diamantprobleem is een ambiguïteit die ontstaat bij meervoudige overerving wanneer twee bovenliggende klassen erven van dezelfde grootouderklasse en beide bovenliggende klassen worden geërfd door een enkele onderliggende klasse. Zonder virtuele overerving zou de onderliggende klasse de eigenschappen van de grootouderklasse twee keer erven, wat leidt tot dubbelzinnigheid.

Dit kan vaak voorkomen in real-world code, dus het is belangrijk om die dubbelzinnigheid aan te pakken wanneer deze wordt opgemerkt.

Het diamantprobleem wordt opgelost met behulp van virtuele overerving, waarbij de virtueel trefwoord wordt gebruikt wanneer bovenliggende klassen erven van een gedeelde grootouderklasse. Door dit te doen, wordt slechts één kopie van de grootouderklasse gemaakt en wordt de objectconstructie van de grootouderklasse gedaan door de onderliggende klasse.

DeelTweetenE-mail
De 10 beste beginnersprojecten voor nieuwe programmeurs

Wil je leren programmeren, maar weet je niet waar te beginnen? Deze programmeerprojecten en tutorials voor beginners zullen je op weg helpen.

Lees volgende

Gerelateerde onderwerpen
  • Programmeren
  • C Programmeren
Over de auteur
MUO-staf

Abonneer op onze nieuwsbrief

Word lid van onze nieuwsbrief voor technische tips, recensies, gratis e-boeken en exclusieve deals!

Klik hier om je te abonneren