Objekte konstruieren

Bisher haben wir noch nichts Neues gesehen im Vergleich zu Strukturen, wie sie in C verwendet werden. Eine C-Struktur ist lediglich eine Beschreibung von Daten. Ein Instanz einer Struktur ist damit erstmal nur einige Daten, die so interpretiert werden, wie es die Struktur beschreibt. Die Daten werden damit aber nicht initialisiert.

Ein Datensatz, der in einer Klasse sitzt, sollte „konstruiert“ werden. Es gibt damit eine Funktion, die das Objekt in einen gültigen Zustand bringt. Nehmen wir eine Klasse „Point“, die eine X- und eine Y-Koordinate beschreibt. Hierfür beschreiben wir zwei Konstruktoren, die dafür sorgen, dass der Datensatz einen gültigen Wert erhält.

Die Headerdatei (point.h):

class Point
{
  public:
    int XPosition;
    int YPosition;
 
    Point();
    Point( int x, int y );
};

Der Quellcode (point.cpp):

Point::Point()    // Default-Konstruktor
{
  XPosition = 0;
  YPosition = 0;
}
 
Point::Point( int x, int y )
{
  XPosition = x;
  YPosition = y; 
}

Die beiden Konstruktoren ermöglicht uns, eine Instanz der Klasse Point auf zwei verschiedene Arten zu konstruieren.

Point * p1 = new Point();
Point * p2 = new Point( 1, 2 );

Der Default-Konstruktor

Der Konstruktor ohne Argumente wird Default-Konstruktor genannt. Er soll die Instanz in einen kontrollierten Zustand bringen.

Copy-Konstruktor

Ein besonderer Konstruktor ist der sogenannte Copy-Konstruktor. Er kopiert ein Objekt. Er hat immer folgende Form:

Class (Class&);

Es muss eine Referenz (wird später im Tutorial erklärt) übergeben werden, da sonst wieder der Copy-Konstruktor aufgerufen werden würde, was eine endlose Rekursion bedeuten würde. Dem Parameter kann noch const vorangestellt werden.

class Point
{
  public:
    int XPosition;
    int YPosition;
 
    Point();
    Point( int x, int y );
 
    Point( Point & p );      // Copy-Konstruktor
};

In unserem Fall bedeutet das einfach die beiden Werte zu kopieren:

Point::Point( Point & p )    // Copy-Konstruktor
{
  XPosition = p.XPosition;
  YPosition = p.YPosition;
}

Es ist wichtig, dass hier wirklich kopiert wird!

Der Copy-Konstruktor wird zur Initalisierung von anderen Instanzen verwendet. Die folgende Idee, eine Liste zu implementieren wird also zu Problemen führen:

class Node
{
  Node * Next;
  Node * Prev;
  List * Owner;
  Data   data;

  Node( Node & previous );
};

Möchte man nun eine neue Node erzeugen und mit dem Konstruktor in die Liste einhängen, so lässt sich das programmieren, aber sobald eine Node kopiert werden soll, so wird die Kopie mit in die Liste eingehängt. Dies führt mit Garantie zu Problemen. Der Konstruktor, der den gleichen Typ als einziges Argument nimmt, muss als Kopierkonstruktor der Daten verwendet werden. Das bedeutet hier, dass die Daten kopiert werden müssen, und die Node selbst in keiner Liste eingehängt wird, damit wirklich nur kopiert wird und keine anderen Datenstrukturen verändert werden.

Aufruf des Copy-Konstruktors

Der Copy-Konstruktor wird in 3 Fällen aufgerufen:

  • Erstellung eines Objekts mit den Daten eines anderen. Dafür gibt es zwei Möglichkeiten:
Point p (q), x = y;
  • Übergabe eines Objekts an eine Funktion (Call by Value):
Point p;
passObject (p);
  • Rückgabe eines Objekts von einer Funktion:
Point p = returnObject ();

Regeln für die Erstellung von Konstruktoren

Konstruktoren

  • haben immer den selben Namen wie die Klasse.
  • haben keinen Rückgabetyp (auch nicht void!).
  • können auch überladen werden.

Generierte Konstruktoren

Der Kopierkonstruktor wird - sofern er nicht vom Entwickler beschrieben wird - automatisch erzeugt. Der generierte kopiert einfach den Datensatz. Das bedeutet, dass auch Zeiger kopiert werden - auch denn, wenn das Objekt, auf das gezeigt wird, ebenfalls kopiert werden müsste. Es wird damit immer eine „flache Kopie“ angelegt. Ist dies nicht gewünscht, muss der Kopierkonstruktor definiert werden.

Wird überhaupt kein Konstruktor definiert, so wird der Standard-Konstruktor generiert, aber dieser initialisiert von sich aus keine Primitive und ruft bei eingebetteten Klassen, wie die Klasse Data, die als data in Node eingebettet ist, grundsätzlich den Standardkonstruktor.

Wenn dies nicht gewünscht ist, muss der Default-Konstruktor beschrieben werden. Wird ein beliebiger Konstruktor definiert, so wird kein Default-Konstruktor generiert. In dem Fall muss ein Objekt über einen der definierten Konstruktoren erzeugt werden.