Referenzen als Member

Referenzen können auch als Membervariablen verwendet werden. Dabei ist eine Referenz genauso groß wie ein Zeiger (also 4 bzw. 8 Bytes für die Adresse), aber sofort gültig initialisiert werden muss.

Schauen wir uns unser Rectangle wieder an und ändern die beiden Point zu Referenzen:

class Rectangle
{
public:
  Point  & TopLeft;
  Point  & BottomRight;
 
  Rectangle( int left, int top, int width, int height );  
  void Print( char const * prefix );
};

Schauen wir auf uns die Implementierung des Konstruktors an:

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

Hier wird versucht der Konstruktor der Points TopLeft und BottomRight zu rufen. Beides sind nun aber keine Points mehr, sondern Referenzen auf Points. Es wird also kein Speicher angefordert, es wird nur die Adresse gespeichert.

Folgendes ist daher ebenfalls nicht möglich:

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

Der GCC-Compiler meldet:

rect.cpp: In constructor ‘Rectangle::Rectangle(int, int, int, int)’:
rect.cpp:6: error: invalid initialization of non-const reference of type ‘Point&’ from a temporary of type ‘Point’
rect.cpp:6: error: invalid initialization of non-const reference of type ‘Point&’ from a temporary of type ‘Point’

Eine Referenz darf nicht auf temporäre Objekte initialisiert werden, die beiden Punkte, die aus left und top bzw. left + width und top + height erzeugt werden, würden nach der Initialisierung wieder entsorgt, also würden die Referenzen danach auf nicht mehr existierende Daten zeigen.

Ähnlich sieht es aus, wenn man kopierte Punkte (Call-By-Value) zur Initialisierung verwendet.
In der Headerdatei zum Rectangle ändern wir den Konstruktor zu:

Rectangle( Point topLeft, Point bottomRight );

und die Implementierung wie folgt:

Rectangle::Rectangle( Point topLeft, Point bottomRight )
  : TopLeft( topLeft )
  , BottomRight( bottomRight )
{
  printf( "Rectangle-Konstruktor\n" );
}

und die Erzeugung eines Punktes:

myRect = new Rectangle( Point( 1, 2 ), Point( 3, 4 ) );
myRect->Print( "myRect" );

Auch hier werden die beiden Punkte nur temporär erzeugen und kopiert. Die Übergabe ist also korrekt. Leider akzeptiert der GCC-Compiler diese Variation, da die Parameter ebenfalls nur temporär erzeugt werden. Der GCC erlaubt in der Implementierung des Rectangle-Konstruktors die Initialisierung der Referenzen mit den temporären Punkte topLeft und bottomRight. Das überraschte mich beim Testen - weniger überrascht mich die Ausgabe der beiden Punkte:

Ambassador:member xin$ ./refcopyparam 
Point: Konstruktor setzt Werte auf 1/2
Point: Konstruktor setzt Werte auf 3/4
Rectangle-Konstruktor
Rectangle myRect contains: 
topleft: 3317/1
bottomRight: 3000/1

Bedauerlicherweise versagt der GNU-C++-Compiler hier, er hätte vor dieser fehlerhafte Initialisierung warnen müssen. Die Instanzen, die auf dem Stack liegen werden nach dem Aufruf des Konstruktors wieder freigegeben. Die Referenzen zeigen in dem Fall also auf den Stack, der anschließend mit anderen Werten überschrieben werden.

So darf man Referenzen nicht belegen und das darf der Compiler eigentlich bemängeln.

Memberreferenzen sollten immer mit Referenzen initialisiert werden:

In der Headerdatei zum Rectangle ändern wir den Konstruktor erneut:

Rectangle( Point & topLeft, Point & bottomRight );

Wie auch die Implementierung:

Rectangle::Rectangle( Point & topLeft, Point & bottomRight )
  : TopLeft( topLeft )
  , BottomRight( bottomRight )
{
  printf( "Rectangle-Konstruktor\n" );
}

Der Aufruf muss sich nun ebenso ändern, denn folgender Aufruf erzeugt temporäre Point-Instanzen

myRect = new Rectangle( Point( 1, 2 ), Point( 3, 4 ) );
myRect->Print( "myRect" );

was auch vom GCC wieder bemängelt wird:

g++ -Wall -c main.cpp
main.cpp: In function ‘int main()’:
main.cpp:28: error: no matching function for call to ‘Rectangle::Rectangle(Point, Point)’
rect.h:12: note: candidates are: Rectangle::Rectangle(Point&, Point&)
rect.h:7: note:                 Rectangle::Rectangle(const Rectangle&)
make: *** [main.o] Error 1

Der Aufruf muss also mit existierenden Referenzen geschehen:

  Point topLeft( 1, 2 );
  Point bottomRight( 3, 4 );
 
  myRect = new Rectangle( topLeft, bottomRight );
 
  myRect->Print( "myRect" );

Hier existieren alle Instanzen, es gibt hier keine Möglichkeit einen Nullpointer zu erzeugen.