ProgrammatieC++ ontwikkelaar

Wat is virtuele overerving in C++ en waarom is het nodig?

Slaag voor sollicitatiegesprekken met de Hintsage AI-assistent

Antwoord.

Geschiedenis van de kwestie

In C++ kunnen klassehiërarchieën het diamantprobleem (diamond problem) veroorzaken, wanneer twee afgeleiden klassen van één basis klasse erven, en vervolgens een andere klasse wordt gemaakt die van deze twee erft. In dat geval bevat een object van de afgeleide klasse twee onafhankelijke kopieën van de basis klasse. Om dit probleem op te lossen, is virtuele overerving in C++ geïmplementeerd.

Probleem

Als je gewone meerdere overerving gebruikt:

class A { public: int x; }; class B : public A {}; class C : public A {}; class D : public B, public C {};

Bevat object D 2 kopieën van A: één via B, de andere via C. Dit veroorzaakt ambiguïteit bij toegang tot A::x en een onterecht geheugenverbruik.

Oplossing

Virtuele overerving elimineert de duplicatie van de basis klasse. Eén instantie van de basis klasse A zal door alle afstammelingen worden gebruikt:

class A { public: int x; }; class B : public virtual A {}; class C : public virtual A {}; class D : public B, public C {};

Nu bevat D slechts één kopie van A, en de ambiguïteit met toegang tot A::x wordt opgelost.

Belangrijkste kenmerken:

  • Het diamantprobleem wordt geëlimineerd
  • Virtuele overerving vertraagt de toegang tot leden van de basis klasse
  • De constructor van de virtuele basis klasse wordt aangeroepen door de constructor van de "verste" afstammeling

Misleidende vragen.

Waarom kan het diamantprobleem niet worden opgelost door expliciet het pad via scope resolution aan te geven?

Scope resolution lost alleen de ambiguïteit op bij toegang tot leden van de basis klasse, maar elimineert niet de overbodige kopieën van de basis klasse zelf. Het probleem van dubbele initiatie en dubbele opslag van gegevens blijft bestaan.

In welke volgorde worden constructors aangeroepen bij virtuele overerving?

Constructors van virtuele basis klassen worden als eerste aangeroepen en slechts één keer, door de constructor van de laatste klasse in de overervingshiërarchie.

Voorbeeld:

class A { public: A() { std::cout << "A\n"; } }; class B : public virtual A { public: B() { std::cout << "B\n"; } }; class C : public virtual A { public: C() { std::cout << "C\n"; } }; class D : public B, public C { public: D() { std::cout << "D\n"; } }; D d; // Output: A B C D

Is het verplicht om virtuele overerving zowel bij de declaratie als bij de definitie van de klasse aan te geven?

Ja, virtuele overerving moet overal worden aangegeven waar sprake is van overerving van de betreffende basis klasse. Een compilatiefout is anders onvermijdelijk, en meerdere overerving zal gewoon zijn.

Typische fouten en anti-patronen

  • Het ontbreken van virtual bij de overerving leidt tot twee instanties van de basis klasse
  • Proberen de constructor van de virtuele basis klasse aan te roepen in alle tussenliggende klassen leidt tot compilatiefouten
  • Misbruik van virtuele overerving maakt hiërarchieën en de onderhoudbaarheid van de code complexer

Voorbeeld uit het leven

Negatieve case

Een complexe hiërarchie waarin geen virtuele overerving is toegepast, waardoor er twee kopieën van de instellingen in het object zijn:

Voordelen:

  • Maakt het gemakkelijk om te compileren en uit te voeren

Nadelen:

  • Het is gemakkelijk om in de configuratiegegevens te verstrikt raken, wat leidt tot defecten van "magisch" dupliceren van basisgegevens

Positieve case

Gebruik van virtuele overerving met een consistente constructor van de hoogste afstammeling:

Voordelen:

  • Geen duplicatie van basisgegevens, gedrag is duidelijk en eenvoudig

Nadelen:

  • Er ontstaat overheadcomplexiteit in het inzien en debuggen voor ontwikkelaars zonder ervaring met virtuele overerving