002
04.07.2008, 11:10 Uhr
ao
(Operator)
|
Zitat von ~UweM: |
C++: |
class B { private: A m_A; public: B(){} B (int i ) ; ~B (); } ;
A::A() { id = 0; cout << " default-konstruktor von A beendet " << endl; }
A::A(int i) { id = i; dat = new float[i]; cout << " spezi-konstruktor von A beendet " << id << endl; }
A::~A() { delete dat; cout << " destruktor von A beendet " << id << endl; id = 0; }
B::B(int i) { m_A = A(i); }
B::~B() { m_A.~A(); }
int main(int argc, char* argv[]) { {
B i_B(9); } getchar() ; return 0; }
|
Das Code-Beispiel liefert folgenden Ausdruck:
default-konstruktor von A beendet spezi-konstruktor von A beendet 9 destruktor von A beendet 9 destruktor von A beendet 9 destruktor von A beendet 0
|
Na, dann wolln wir mal gucken.
Die Zeile "B i_B(9);" bewirkt folgendes:
B::B(int i) Konstruktion von B und Default-Konstruktion aller Member von B. "default-konstruktor von A beendet" { m_A = A(i); Konstruktion eines neuen A (mit Argument i) "spezi-konstruktor von A beendet 9"
... und Kopieren der neuen Instanz in die Member-Variable m_A. Vorsicht, mehrere Fallen:
1. Da du keinen expliziten Kopier-Konstruktor geschrieben hast, wird vom Compiler einer generiert, der einfach nur elementweise kopiert. Das ist in Trivialfällen ausreichend, muss aber sehr genau beäugt werden, sobald Resourcen-Management im Spiel ist (dynamischer Speicher, offene Dateien, ...). Sonst referenzieren mehrere Instanzen dasselbe Resourcen-Objekt, und es kracht spätestens, wenn die Resource mehrfach freigegeben wird. Du musst einen sog. Kopier-Konstruktor schreiben, der sich drum kümmert, dass die Inhalte des Quellobjekts richtig herauskopiert werden.
2. Die Variable m_A enthält bereits ein Default-konstruiertes Objekt, das durch die Zuweisung verlorengeht. Auch hier: Bei Trivialobjekten kein Schaden, aber wenn das Objekt Resourcen festhält, können diese danach nicht mehr erreicht werden (Speicherleck, verlorene Dateien, ...). Das ist der Grund, warum du einen operator=() brauchst: Vor der Übernahme eines neuen Objekts muss das Zielobjekt alte Resourcen loslassen.
Richtig wäre die Verwendung einer sog. Initialisierungsliste: B::B(int i) : m_A (i) { } Das würde ohne Zwischenschritte sofort das gewünschte A-Objekt erzeugen.
Weiter gehts an dieser Stelle: } mit dem Zerlegen des B-Objekts. Das geschieht automatisch an der schließenden Klammer, da der Lebensbereich des Objekts verlassen wird. ~B wird aufgerufen:
B::~B() { m_A.~A(); Dieser Aufruf gehört hier nicht hin. Es ist in 99,9 % der Fälle falsch, Destruktoren explizit aufzurufen (*). Entweder werden sie automatisch vom Compiler gerufen (wenn der Sichtbarkeitsbereich des Objekts verlassen wird), oder der Programmierer ruft sie mittelbar über "delete". Oder er vergisst es, das ist dann ein Leck. (*) In der Microsoft-ATL gibt es Collection-Implementierungen, die ihre enthaltenen Kind-Objekte per ~T zerlegen. Ich habe auch sehr gestaunt, als ich das zum ersten Mal sah. } An dieser Stelle ruft der Compiler die Destruktoren aller Member von B, also auch den von m_A. Da du das vorher schon getan hast, wird es hier knallen. Dieser Post wurde am 04.07.2008 um 11:11 Uhr von ao editiert. |