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.
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.
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:
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) { ... }
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:
Nadelen:
Een ontwikkelaar implementeert correct de kopieconstructor, de toewijsoperator en de destructor. Elk object beheert zijn eigen geheugen.
Voordelen:
Nadelen: