Kopieren eines Objekts

Content

Es gibt zwei Wege, wie ein Objekt kopiert werden kann, die vom Zielobjekt abhängen:

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

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:

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:

class Vector2D {
   public:
      Vector2D() : x(0), y(0) {
      }
      Vector2D(const Vector2D& other) = delete;
      double x, y;
};

int main() {
   Vector2D a;
   Vector2D b = a;
}
theon$ g++ -Wall -c deleted-copy-constructor.cpp
deleted-copy-constructor.cpp: In function 'int main()':
deleted-copy-constructor.cpp:11:17: error: use of deleted function 'Vector2D::Vector2D(const Vector2D&)'
    Vector2D b = a;
                 ^
deleted-copy-constructor.cpp:5:7: note: declared here
       Vector2D(const Vector2D& other) = delete;
       ^~~~~~~~
theon$ 

Der Übersetzer verwendet den Kopierkonstruktor gerne implizit beim Anlegen eines Objekts und bei der Parameterübergabe:

#include <iostream>

class Vector2D {
   public:
      Vector2D() : x(0), y(0) {
	 std::cout << "X: default constructor" << std::endl;
      }
      Vector2D(const Vector2D& other) : x(other.x), y(other.y) {
	 std::cout << "X: copy constructor" << std::endl;
      }
      double x, y;
};

void foo(Vector2D v) {
}

int main() {
   Vector2D a;
   Vector2D b = a; // equivalent to: Vector2D b{a}
   foo(b);
}
theon$ g++ -Wall -o copy-constructor copy-constructor.cpp
theon$ ./copy-constructor
X: default constructor
X: copy constructor
X: copy constructor
theon$ 

Zuweisung

Ein expliziter Zuweisungsoperator für Vector2D, der ansonsten implizit durch den Übersetzer erzeugt worden wäre, sieht so aus:

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:

#include <iostream>

class Vector2D {
   public:
      Vector2D() : x(0), y(0) {
	 std::cout << "X: default constructor" << std::endl;
      }
      Vector2D(const Vector2D& other) : x(other.x), y(other.y) {
	 std::cout << "X: copy constructor" << std::endl;
      }
      Vector2D& operator=(const Vector2D& other) {
	 std::cout << "X: assignment operator" << std::endl;
	 x = other.x; y = other.y;
	 return *this;
      }
      double x, y;
};

int main() {
   Vector2D a, b, c;
   c = b = a;
}
theon$ g++ -Wall -o assignment-operator assignment-operator.cpp
theon$ ./assignment-operator
X: default constructor
X: default constructor
X: default constructor
X: assignment operator
X: assignment operator
theon$ 

Aufgaben