Lektion 61:

Copy-Konstruktor

Die Klasse Vector sah nur schon fast fertig aus. Leider hat sie noch einen gravierenden Mangel. In den folgenden Lektionen wird es uns immer wieder so ergehen, daß eine eigentlich recht schöne Klasse aus einem gar nicht offensichtlichen Grund nicht so funktioniert, wie man es haben möchte, oder daß die Klasse nicht so praktisch ist, wie sie anfangs erscheint. Die Probleme, die ich aufzeigen werde, sind eher allgemeiner Natur und nicht spezielle Probleme eines Telefonbuch-Programmes. Freuen Sie sich also darauf, daß Sie nach Abschluß des Telefonbuch-Programmes eigene Klassen bauen können, welche die schlimmsten Fehler von Anfang an nicht haben werden.

Der Fehler in der Vector-Klasse zeigt sich recht deutlich in folgendem Programm:

void main()
{
   Vector v1(10);
   v1[1]=4711;
   v1[2]=0;
   Vector v2(v1);
   v2[2]=4712;
   cout<<v1[1]<<endl;
   cout<<v1[2]<<endl;
};

Dieses Programm stürzt ab!

Der Grund dafür liegt in der Zeile

Vector v2(v1);

 Beabsichtigt ist, daß ein neuer Vector v2 angelegt wird, der eine genaue Kopie des bereits existierenden Vectors v1 ist. Dieser Aufruf ist ein normaler Konstruktor-Aufruf. Eigentlich dürfte er nicht funktionieren, denn wir haben keinen Konstruktor geschrieben, der Objekte vom Typ Vector als Parameter annimmt. Einen solchen Konstruktor nennt man Copy-Konstruktor, denn er hat die Aufgabe, eine Kopie des betreffenden Objektes zu erstellen. Offensichtlich existiert solch ein Copy-Konstruktor bereits, ohne daß im Programm steht.

Der Compiler erzeugt automatisch einen Copy-Konstruktor.

Der automatisch erzeugte Copy-Konstruktor geht ganz einfach so vor, daß er für alle Attribute des neu zu konstruierenden Objektes die entsprechenden Copy-Konstruktoren mit den entsprechenden Attributen des übergebenen Objektes aufruft. Diese Vorgehen ist auch für die meisten Klassen sinnvoll. Für die Vector-Klasse ist dieses Vorgehen allerdings nicht korrekt.

Nach der Zeile

Vector v1(10);

sind die beiden Attribute v1.m_data und v1.m_size korrekt gesetzt. Die Zeilen

   v1[1]=4711;
   v1[2]=0;

schreiben dann in den Speicherbereich, auf den v1.m_data zeigt, auf den Positionen 1 und 2 die Werte 0 und 4711. Soweit ist noch alles in Ordnung. Die Zeile

Vector v2(v1);

bewirkt jetzt (durch den automatisch erzeugten Copy-Konstruktor) das Gleiche wie:

   v2.m_size=v1.m_size;
   v2.m_data=v1.m_data;

Die beiden Vectoren nutzen also auf den selben Speicherbereich. Das wird mit den folgenden Zeilen deutlich:

   v2[2]=4712;
   cout<<v1[1]<<endl;
   cout<<v1[2]<<endl;

Es werden nämlich die Zahlen 4711 und 4712 ausgegeben, obwohl die Zahl 4712 nur in den Vector v2 geschrieben wurde, wird sie bei v1 ausgegeben. Als nächstes werden bei der schließenden geschweiften Klammer die beiden Vectoren gelöscht. Dazu wird jeweils der Destruktor aufgerufen.

Zuerst wird v2 gelöscht. Dabei wird im Destruktor der Speicherbereich, auf den v2.m_data zeigt, mit delete[] gelöscht.

Dann wird v1 gelöscht. Dabei wird im Destruktor der Speicherbereich, auf den v1.m_data zeigt, mit delete[] gelöscht. Aber das ist derselbe Speicherbereich, der bereits im Destruktor von v2 gelöscht wurde. Man darf aber einen einmal gelöschten Bereich nicht nochmals löschen, und schon ist der Rechner abgestürzt.

Um dies zu vermeiden, muß man sich nur einen Copy-Konstruktor schreiben, der eine richtige Kopie anlegt. Nachdem Sie in der letzten Lektion bereits die grow-Methode geschrieben haben, sollte das kein Problem sein:

   Vector(Vector derAndereVector)
   {
      m_size=derAndereVector.m_size;
      m_data=new double[m_size];
      for(int i=0;i<m_size;++i)
         m_data[i]=derAndereVector.m_data[i];
   };

Und das erstaunliche Ergebnis ist eine wirklich überraschende Fehlermeldung!

==> 'Vector' : illegal copy constructor: first parameter must not be a 'Vector'

Damit hat der Compiler wie immer Recht. Nur ist die Begründung wieder einmal nicht ganz offensichtlich:

Der Parameter derAndereVector wurde by value übergeben. Das heißt also, daß die Variable derAndereVector innerhalb dieser Methode eine lokale Variable ist, die eine Kopie des übergebenen Vectors v1 beinhaltet. Und wie wird diese Kopie erstellt? Natürlich mit dem Copy-Konstruktor. Aber der kann nicht ausgeführt werden, ohne daß noch ein Copy-Konstruktor vorher aufgerufen wird und so weiter...

So geht es also nicht. Der Parameter derAndereVector darf nicht by value übergeben werden. Es bleibt also der Aufruf by reference. Da nicht beabsichtigt ist, den übergebenen Vector v1 zu verändern wird eine const-Referenz verwendet.

   Vector(const Vector &derAndereVector)
   {
      m_size=derAndereVector.m_size;
      m_data=new double[m_size];
      for(int i=0;i<m_size;++i)
         m_data[i]=derAndereVector.m_data[i];
   };

Heureka! Das Programm funktioniert. Den nächsten Absturz dürfen Sie dann in der folgenden Lektion genießen.



Falls Ihnen Fehler im Text auffallen oder Sie Verbesserungsvorschläge haben, dann schicken Sie mir bitte eine Mail. Ich werde mich dann sofort darum kümmern.
[aktuelle Version] [inhalt] [index]      [Fehlerkorrektur, Verbesserungsvorschlag]

© Volkard Henkel <volkard@normannia.de>, last update: 08/25/00