====================== Kopieren eines Objekts [TOC] ====================== Es gibt zwei Wege, wie ein Objekt kopiert werden kann, die vom Zielobjekt abhängen: * Wenn das Zielobjekt noch nicht existiert, kann es kopier-konstruiert werden. * Wenn hingegen das Zielobjekt bereits konstruiert ist, kann es mit der Kopie komponentenweise überschrieben werden. Beide Varianten werden durch den Übersetzer für jede Klasse zur Verfügung gestellt, wenn dem keine Hindernisse in Wege stehen. Zu den Hindernissen gehören * das explizite Löschen der entsprechenden Methode, * Komponenten, die die entsprechende Methode nicht unterstützen, oder * die eigene Definition der entsprechenden Methode. Wir werfen hintereinander einen Blick auf beide Varianten: Kopierkonstruktor ================= Ein Kopierkonstruktor erhält eine `const`-Referenz auf ein anderes Objekt des gleichen Typs. Bei allen elementaren Datentypen und Zeigern ist die Operation vordefiniert, wenn dem kein Hindernis entgegensteht. Hier ist ein Beispiel eines expliziten Kopierkonstruktors, der genau das tut, was auch der vom Übersetzer automatisch erzeugte Kopierkonstruktor getan hätte: ---- CODE (type=cpp) ---------------------------------------------------------- class Vector2D { public: Vector2D(const Vector2D& other) : x(other.x), y(other.y) { } double x, y; }; ------------------------------------------------------------------------------- Das explizite Hinzufügen des Kopierkonstruktors hat jetzt aber eine möglicherweise unerwartete Nebenwirkung. Wissen Sie welche? Wenn wir möchten, können wir den Kopierkonstruktor explizit verbieten: :import: session02/deleted-copy-constructor.cpp ---- SHELL (path=session02,hostname=theon) --------------- g++ -Wall -c deleted-copy-constructor.cpp ---------------------------------------------------------- Der Übersetzer verwendet den Kopierkonstruktor gerne implizit beim Anlegen eines Objekts und bei der Parameterübergabe: :import: session02/copy-constructor.cpp ---- SHELL (path=session02,hostname=theon) --------------- g++ -Wall -o copy-constructor copy-constructor.cpp ./copy-constructor ---------------------------------------------------------- Zuweisung ========= Ein expliziter Zuweisungsoperator für _Vector2D_, der ansonsten implizit durch den Übersetzer erzeugt worden wäre, sieht so aus: ---- CODE (type=cpp) ---------------------------------------------------------- class Vector2D { public: Vector2D& operator=(const Vector2D& other) { x = other.x; y = other.y; return *this; } double x, y; }; ------------------------------------------------------------------------------- Überladene Operatoren sind ganz normale Funktionen oder Methoden in C++, die den Namen `operator` gefolgt von dem jeweiligen Operator haben. Wie beim Kopierkonstruktor wird das Objekt, von dem kopiert werden soll, normalerweise als `const`-Referenz erwartet. Die vom Übersetzer erzeugte Fassung kopiert dann komponentenweise. Im Vergleich zum Kopierkonstruktor ist hier noch der Rückgabetyp interessant. Das Schlüsselwort `this` liefert einen Zeiger zum eigenen Objekt, das ist hier vom Datentyp `Vector2D*`. (Idealerweise würde `this` eine Referenz liefern, aber historisch gesehen kam in C++ zuerst das Schlüsselwort `this`, bevor Referenzen eingeführt worden sind.) Wenn ein Zeiger dereferenziert wird, haben wir noch eine sogenannte _lvalue_, d.h. ein Wert, der links von einer Zuweisung stehen kann. Im Typsystem von C++ sind alle _lvalues_ Referenzen, d.h. `*this` hat den Datentyp `Vector2D&`. Wenn der Rückgabetyp diesen Wert hat, können wir dementsprechend auch den Funktionsaufruf als _lvalue_ behandeln, d.h. der Aufruf darf auf der linken Seite einer Zuweisung stehen. Das erlaubt beim rechts-assoziativen Zuweisungsoperator eine Kaskadierung der Zuweisungsoperatoren, die von C++ auch für die elementaren Datentypen unterstützt wird: :import: session02/assignment-operator.cpp ---- SHELL (path=session02,hostname=theon) --------------- g++ -Wall -o assignment-operator assignment-operator.cpp ./assignment-operator ---------------------------------------------------------- Aufgaben ======== * Im folgenden Beispiel wurde der Kopierkonstruktor explizit gelöscht. Gibt das einen Fehler? Wenn ja, wie kann dieser behoben werden, ohne die Klasse _Vector2_ zu verändern? :import: session02/case06.cpp * Die Klasse _X_ im folgenden Beispiel weder einen expliziten Kopierkonstruktor noch einen Zuweisungsoperator. Beides wird vom Übersetzer gestellt. Leider führt die Ausführung zu mehrfachen Freigaben der gleichen Speicherfläche, was nicht zulässig ist. Wie genau kommt es dazu? Und wie lässt sich das verhindern, indem die Klasse _X_ geeignet ergänzt wird und _main_ unverändert bleibt? :import: session02/data-on-heap.cpp [linenumbers] ---- SHELL (path=session02,hostname=heim) ---------------- g++-8.3 -Wall -g -o data-on-heap data-on-heap.cpp ./data-on-heap valgrind ./data-on-heap ---------------------------------------------------------- Die Standard-Bibliothek für die Speicherverwaltung unter Linux entdeckt gelegentlich Fehler wie die mehrfache Freigabe der gleichen Speicherfläche. _valgrind_ untersucht solche Fehler systematisch und stellt auch Speicherlecks fest. :navigate: up -> doc:index back -> doc:session02/page04 next -> doc:session02/page06