Kopieren verboten!
Beim Programmieren taucht öfter mal die Anforderung auf, Kopien von bestimmten Objekten zu verhindern. Das können bspw. große Objekte sein, die man aus Effizienzgründen nicht kopieren will oder Betriebssystemressourcen, die man nicht kopieren kann/sollte (Mutexe oder Filehandles sind da immer gute Kandidaten). Dummerweise hilft einem C++ beim Kopieren von Objekten im Normalfall, indem es die dafür notwendigen Memberfunktionen automatisch anlegt, was natürlich dann nicht mehr gewollt ist. Das kann man aber auch verhindern.
Video
Code
|
|
Erklärung
C++ generiert einige Memberfunktionen, die man typischerweise gebrauchen kann, automatisch. Dazu gehören:
- Standardkonstruktor – Falls kein anderer Konstruktor definiert ist
- Copy-Konstruktor – Falls kein eigener Copykonstruktor (oder Movekonstruktor bei C++11) definiert wurde. Sonderregel hier noch: wenn ein eigener Destruktor definiert wurde, dann ist bei C++11 das Fehlen eines eigenen Copykonstruktors als veraltet deklariert. Sprich: das könnte dann mit der nächsten Standardvariante ein Fehler sein.
- Destruktor – Falls kein eigener erzeugt wurde
- Zuweisungsoperator (Copy, bzw. Copy und Move bei C++11) – Falls kein eigener definiert wurde
Im Standardfall macht jedes dieser Memberfunktionen mit den Feldern der Klasse genau das, was sie selbst tut: sprich: der
Copy-Konstruktor ruft die Copy-Konstruktoren aller Member auf, der Destruktor die Destruktoren etc. Genau dieses Verhalten
ist hier unser Problem: wird der Copykonstruktor von unserer Klasse aufgerufen (bspw. durch den call-by-value-Parameter der
Funktion printBuffer
), dann ruft er standardmäßig die Copykonstruktoren der Felder auf. Das ist im Falle des
mSize
-Feldes kein Problem: das kopiert einfach den Wert. Im Fall des mBuffer
-Pointers allerdings sieht die Sache anders
aus: statt den Speicherbereich zu kopieren, auf den der Pointer zeigt, wird nur der Wert des Pointers kopiert. Damit zeigen
dann natürlich zwei Pointer auf den gleichen Speicherbereich. Das ist an sich erstmal noch kein Problem, kann sogar ja
gewollt sein. Problematisch wird es dann, wenn eines der Objekte vernichtet wird: der Destruktor gibt den Speicherbereich
frei. Damit zeigen alle Kopien plötzlich ins Nirvana. Werden diese nun vernichtet, versucht das Programm einen bereits
freigegebenen Speicherbereich nochmals freizugeben. Das ist laut Standard undefiniertes Verhalten, kann also im Prinzip
beliebiges Verhalten zeigen (von einfachem Funktionieren bis hin zum Programmabsturz).
Dem Problem kann man mit zwei Mitteln begegnen: entweder man definiert einen eigenen Copykonstruktor, der den
Speicherbereich tatsächlich kopiert oder (wie in unserem Fall hier) man entscheidet, dass eine Kopie verboten ist und
definiert seine Klasse entsprechend. Diesen Weg haben wir hier gewählt, weil so ein IntBuffer
ja beliebig groß werden kann
und wir den daher nicht kopieren wollen. Hier gibt es nun zwei Möglichkeiten: entweder man definiert den Copykonstruktor als
private
(wie oben im Beispiel auskommentiert in Zeile 11) oder man verbietet dem Compiler explizit mit = delete
die
automatische Implementierung des Konstruktors (geht nur in C++11, siehe Zeile 21 im Beispiel). Versucht man nun eine Kopie
eines IntBuffer
-Objektes anzulegen, dann wird sich der Compiler beschweren, dass er den Copy-Konstruktor nicht aufrufen
darf (bzw. dass dieser gelöscht wurde bei der C++11-Variante).