Historie van de kwestie:
C++ heeft het concept van klasovererving overgenomen van C++ en objectgeoriënteerde methodologieën. De opkomst van meerdere en virtuele overerving heeft de structuur van hiërarchieën gecompliceerd, wat de flexibiliteit vergrootte, maar nieuwe foutklassen toevoegde.
Probleem:
Directe overerving is de situatie waarin een klasse rechtstreeks van een andere erft zonder extra complicaties. Indirecte overerving doet zich voor wanneer geërfde leden via een of meerdere tussenliggende overervingsketens komen. De belangrijkste moeilijkheid is het "diamond problem", waarbij verschillende paden naar één basisklasse kunnen leiden tot duplicatie van zijn leden in afgeleide klassen.
Oplossing:
Om de complexiteit te beheersen, wordt virtuele overerving gebruikt, die zorgt voor één gemeenschappelijk exemplaar van het lid van de basisklasse voor de hele hiërarchie, in plaats van voor elke keten.
Code voorbeeld:
class A { public: int value; }; class B : public virtual A {}; class C : public virtual A {}; class D : public B, public C {};
In de klasse D zal er slechts één exemplaar zijn van het lid A::value.
Belangrijke kenmerken:
Kan virtuele overerving onvoorspelbaar gedrag veroorzaken als deze niet wordt gebruikt bij een diamond probleem?
Als er in een diamond hiërarchie geen gebruik wordt gemaakt van virtuele overerving, zullen de leden van de basisklasse worden gedupliceerd. Dit kan de logica van de code verwarren en leiden tot ambiguïteit bij de toegang tot de leden van de basisklasse.
Wanneer is het niet nodig om virtuele overerving te gebruiken?
Als u zeker weet dat uw hiërarchieën geen diamond structuur vormen, of als de basisklasse geen gegevens bevat, is virtuele overerving niet vereist.
Hoe beheert u het aanroepen van constructors bij virtuele overerving?
De constructor van de virtuele basisklasse wordt slechts door de "laagste" afgeleide klasse aangeroepen. In tussenklassen is het verboden om argumenten voor de virtuele basisklasse op te geven (de constructor wordt standaard aangeroepen als deze niet expliciet in de eindklasse wordt geïnitialiseerd).
Code voorbeeld:
class A { public: A(int x) { /* ... */ } }; class B : public virtual A { public: B() : A(0) {} // fout: kan A hier niet initialiseren }; class D : public B { public: D() : A(10), B() {} // correct };
In een groot project merkte de ontwikkelaar de opkomst van een diamond structuur niet op - beide tussenklassen erft direct van één basis. Toegang tot het lid van de basisklasse veroorzaakte ambiguïteit, de code was moeilijk leesbaar.
Voordelen:
Nadelen:
De architect merkte het probleem vroegtijdig op en gebruikte virtuele overerving voor de tussenklassen, en de constructor van de virtuele basisklasse werd correct aangeroepen alleen in de laagste afgeleide klasse.
Voordelen:
Nadelen: