Die Initialisierungsliste

Kommen wir nochmals zu den Beispielen mit den graphischen Objekten. Konstruieren wir ein Rechteck, dass einen Startpunkt links oben und einen Endpunkt rechts unten hat.

Einen Punkt haben wir bereits als Klasse beschrieben:

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

und im Quelltext:

Point::Point()    // Default-Konstruktor
{
  printf( "Point: Default-Konstruktor\n" );
  XPosition = 0;
  YPosition = 0;
}
 
Point::Point( int x, int y )
{
  printf( "Point: Konstruktor setzt Werte auf %d/%d\n", x, y );
  XPosition = x;
  YPosition = y; 
}

Nun bauen wir ein Rechteck:

class Rectangle
{
public:
  Point    TopLeft;
  Point    BottomRight;
 
  Rectangle( int left, int top, int width, int height );
};

und als Quelltext:

Rectangle::Rectangle( int left, int top, int width, int height )
{
  printf( "Rectangle-Konstruktor\n" );
 
  printf( "Rectangle: Setze Topleft auf %d/%d\n", left, top );
  TopLeft.XPosition = left;
  TopLeft.YPosition = top;
 
  printf( "Rectangle: Setzte ButtomRight auf %d/%d\n", left + width, top + height );
  BottomRight.XPosition = left + width;
  BottomRight.YPosition = top + height;
}

In diesem Projekt1) gibt jeder Konstruktor eine Zeile aus. Im Projekt werden zusätzlich die Wir sehen folgende Ausgabe:

Ambassador:defaultconstruct xin$ ./defaultconstruct 
Point: Default-Konstruktor
Point: Default-Konstruktor
Rectangle-Konstruktor
Rectangle: Setze Topleft auf 1/2
Rectangle: Setzte ButtomRight auf 4/6
Rectangle myRect contains: 
topleft: 1/2
bottomRight: 4/6

Wir sehen zwei wichtige Dinge:

  • Zum einen werden die Konstruktoren der eingebetteten Objekte gerufen, bevor der Konstruktor des eigentlich zu konstruierenden Objektes ausgeführt wird.
  • Das Programm macht eine Dummheit: Der Defaultkonstruktor initialisiert die Punkte mit Null, während der Konstruktor des Rectangles die soeben initialisierten Punkte direkt wieder überschreibt.

Den ersten Punkt müssen wir uns merken: dies ist erforderlich, um Instanzen aufeinander aufbauend zu konstruieren. Den zweiten Punkt aber können wir beeinflussen. Hierfür eignet sich die Initliste. Dabei wird genau der Konstruktor festgelegt, der gerufen werden soll.

Hierfür ändern wir den Konstruktor von Rectangle nur geringfügig:

Rectangle::Rectangle( int left, int top, int width, int height )
  : TopLeft( left, top )
  , BottomRight( left + width, top + height )
{
  printf( "Rectangle-Konstruktor\n" );
}

Nach der Deklaration des Konstruktors folgt ein Doppelpunkt und anschließend die Initialisierungsliste. In der Liste werden die Konstruktoren der eingebetteten Datentypen gerufen.

Genauso lassen sich auch Primitive, wie die beiden Integer im Point initialisieren:

Point::Point()    // Default-Konstruktor
  : XPosition(0)
  , YPosition(0)
{
  printf( "Point: Default-Konstruktor\n" );
}
 
Point::Point( int x, int y )
  : XPosition( x )
  , YPosition( y )
{
  printf( "Point: Konstruktor setzt Werte auf %d/%d\n", x, y );
}

Es entsteht ein gültig initialisierter Point. In der eigentlichen Konstruktor-Funktion bleibt nur die Ausgabe übrig. Auch dieses Projekt lässt sich komplett herunterladen.2)

Nach dem wir diese kleinen Änderungen haben wir folgende Ausgabe:

Ambassador:initconstruct xin$ ./initconstruct 
Point: Konstruktor setzt Werte auf 1/2
Point: Konstruktor setzt Werte auf 4/6
Rectangle-Konstruktor
Rectangle myRect contains: 
topleft: 1/2
bottomRight: 4/6

Nun fällt der Aufruf des Standardkonstruktors von Point weg und es wird der Konstruktor gerufen, der die jeweilige Instanz von Point gleich auf die richtigen Werte setzt. Das Setzen der gewünschten Werte im Rectangle-Konstruktor wird damit überflüssig. Wir sparen also die Rechenleistung, die notwendig ist, die Punkte im Standardkonstruktor mit 0 zu initialisieren.

Eingebettete Instanzen sollten also immer über Initialisierungsliste initialisiert werden.

1) Defaultkonstruktoren: Download
2) Initialisierungslisten: Download