ProgrammatieC++ ontwikkelaar

Leg het verschil uit tussen shallow copy en deep copy voor klassen met dynamisch geheugen. Wanneer en waarom is een diepe kopieconstructor nodig? Hoe implementeer je deep copy handmatig?

Slaag voor sollicitatiegesprekken met de Hintsage AI-assistent

Antwoord.

Geschiedenis van de vraag

In C++ wordt bij het kopiëren van objecten oorspronkelijk gebruik gemaakt van lid-gewijze kopie: voor elk lid van het object wordt de eigen kopie-operatie aangeroepen. Voor ingebouwde types is dit veilig, maar voor dynamische bronnen ontstaat een probleem: alleen de aanwijzers worden gekopieerd, maar niet de daadwerkelijke gegevens.

Probleem

Als een object een aanwijzer bevat naar geheugen dat in de heap is toegewezen, zullen na het kopiëren van twee objecten ze naar hetzelfde geheugenadres verwijzen. Bij het vernietigen van een van de objecten wordt het geheugen vrijgegeven en wordt de aanwijzer van de ander ongeldig ("wilde" aanwijzer). Dit leidt tot runtime-fouten en geheugenlekken.

Oplossing

Om een onafhankelijk exemplaar te verkrijgen, is een deep copy noodzakelijk - bytegewijze kopie en het toewijzen van een eigen buffer. Dit wordt gerealiseerd door het schrijven van een aangepaste kopieconstructor en toewijsoperator.

Voorbeeldcode:

class MyString { char* data; public: MyString(const char* s) { data = new char[strlen(s)+1]; strcpy(data, s); } // Diepe kopieconstructor MyString(const MyString& src) { data = new char[strlen(src.data) + 1]; strcpy(data, src.data); } // Diepe toewijsoperator MyString& operator=(const MyString& src) { if (this != &src) { delete[] data; data = new char[strlen(src.data) + 1]; strcpy(data, src.data); } return *this; } ~MyString() { delete[] data; } };

Belangrijke kenmerken:

  • Vereist voor klassen die externe bronnen beheren
  • Vereist de implementatie van eigen kopieconstructor en operator=
  • Mogelijke geheugenlekken zonder correcte implementatie van de Rule of Three

Vragen met een val

Waarom is een aangepaste destructor nodig als de klasse alleen een aanwijzer bevat, maar geen geheugen wordt toegewezen?

Een destructor is alleen nodig als je expliciet geheugen (of een andere bron) binnen de klasse hebt toegewezen. Als de aanwijzer geen geheugen toewijst, is de standaard destructor voldoende.


Wat gebeurt er als je operator= niet implementeert in een klasse met dynamisch geheugen, maar de kopieconstructor is gedefinieerd?

Als je de kopieconstructor handmatig hebt gedefinieerd, zal de compiler operator= niet automatisch implementeren; deze wordt ofwel impliciet gedefinieerd, of de compiler geeft een fout/warning (afhankelijk van de standaard). Dit leidt tot slecht gedefinieerd gedrag bij toewijzing: er vindt een lid-gewijze kopie plaats en er ontstaat een double free of geheugenlek.

Voorbeeldcode:

MyString a("hi"); MyString b = a; // Ok: jouw kopieconstructor MyString c("bye"); c = a; // Probleem! Als operator= niet handmatig is geïmplementeerd, zal er een shallow copy zijn

Wat is het gevaar van zelftoewijzing bij het handmatig implementeren van operator=?

Als bronnen worden gedeeld zonder te controleren op this!=&rhs, dan zal bij zelftoewijzing delete[] data worden aangeroepen en daarna een kopie van een al vernietigd array worden gemaakt, wat leidt tot een segfault. Zelfbescherming: controleer altijd op zelftoewijzing.

if (this != &rhs) { ... }

Typische fouten en anti-patronen

  • Een van de Rule of Three-methoden (kopieconstructor, operator=, destructor) is niet geïmplementeerd
  • Zelftoewijzing wordt niet gecontroleerd
  • Geheugenlek bij een vergeten delete[]
  • Double free als aanwijzers gemeenschappelijk zijn

Voorbeeld uit het leven

Negatief geval

Een ontwikkelaar kopieert een object van een klasse met een ingebouwde aanwijzer zonder een deep copy te implementeren. Na kopiëren delen meerdere objecten hetzelfde geheugenadres. Twee destructors leiden tot een double delete, het programma crasht.

Voordelen:

  • Gemakkelijk om de code te schrijven ("kopiëren werkt" op het eerste gezicht)

Nadelen:

  • Programmacrash, onvoorspelbare bugs
  • Geheugenlekken of double-free

Positief geval

Een ontwikkelaar implementeert correct de kopieconstructor, de toewijsoperator en de destructor. Elk object beheert zijn eigen geheugen.

Voordelen:

  • Veiligheid bij kopiëren en vernietigen
  • Geen lekken

Nadelen:

  • Bij een groot aantal kopieën verhoogt de overhead van kopiëren