Es wird das gleiche tun (nichts, im Wesentlichen). Aber es ist nicht dasselbe, als wenn du es nicht geschrieben hättest. Da das Schreiben des Destruktors einen funktionierenden Basisklassen-Destruktor erfordert. Wenn der Basisklassendestruktor privat ist oder aus einem anderen Grund nicht aufgerufen werden kann, ist Ihr Programm fehlerhaft. Betrachten Sie diese
struct A { private: ~A(); };
struct B : A { };
Das OK ist, solange Ihr nicht erfordern ein Objekt vom Typ B zu zerstören (und damit implizit vom Typ A) - wie wenn Sie noch nie auf einem dynamisch erstellte Objekt löschen aufrufen, oder du erstellst niemals ein Objekt von ihm. Wenn Sie dies tun, zeigt der Compiler eine entsprechende Diagnose an. Nun, wenn Sie eine sehen ausdrücklich
struct A { private: ~A(); };
struct B : A { ~B() { /* ... */ } };
Dass man wird versuchen, implizit den Destruktor der Basisklasse aufrufen, und wird eine Diagnose bereits zur Definitionszeit von ~B
verursachen.
Es gibt einen weiteren Unterschied, der sich um die Definition des Destruktors und implizite Aufrufe von Elementdestruktoren dreht.Betrachten Sie dieses Smart-Pointer-Mitglied
struct C;
struct A {
auto_ptr<C> a;
A();
};
Lassen Sie sich das Objekt vom Typ annimmt C
wird in der Definition von A Konstruktor in der .cpp
-Datei erstellt, die auch die Definition der Struktur enthalten C
. Wenn Sie jetzt die Struktur A
verwenden und die Zerstörung eines Objekts A
erfordern, stellt der Compiler wie im obigen Fall eine implizite Definition des Destruktors bereit. Dieser Destruktor ruft auch implizit den Destruktor des Objekts auto_ptr auf. Und das wird den Zeiger löschen, der es enthält, der auf das Objekt C
zeigt - ohne die Definition von C
zu kennen! Das erschien in der Datei .cpp
, wo der Konstruktor von struct A definiert ist.
Dies ist tatsächlich ein häufiges Problem bei der Implementierung der Pimpl-Idiom. Die Lösung besteht darin, einen Destruktor hinzuzufügen und eine leere Definition davon in der Datei anzugeben, in der die Struktur C
definiert ist. Zu dem Zeitpunkt, zu dem es den Destruktor seines Members aufruft, wird es die Definition der Struktur C
kennen und kann seinen Destruktor korrekt aufrufen.
struct C;
struct A {
auto_ptr<C> a;
A();
~A(); // defined as ~A() { } in .cpp file, too
};
Beachten Sie, dass boost::shared_ptr
dieses Problem nicht hat: Es erfordert stattdessen eine komplette Art, wenn sein Konstruktor in gewisser Weise aufgerufen wird.
Ein weiterer Punkt, wo es einen Unterschied in aktuellen C++ macht, ist, wenn Sie memset
und Freunde auf ein solches Objekt verwenden möchten, das einen vom Benutzer deklarierten Destruktor hat. Solche Typen sind keine PODs mehr (einfache alte Daten), und diese dürfen nicht bitkopiert werden. Beachten Sie, dass diese Einschränkung nicht wirklich benötigt wird - und die nächste C++ - Version hat die Situation in diesem Fall verbessert, so dass Sie diese Typen immer noch bitkopieren können, solange andere wichtigere Änderungen nicht vorgenommen werden.
Da Sie nach Konstrukteuren gefragt haben: Nun, für diese sind die gleichen Dinge wahr. Beachten Sie, dass Konstruktoren auch implizite Aufrufe von Destruktoren enthalten. Bei Dingen wie auto_ptr machen diese Aufrufe (auch wenn sie zur Laufzeit nicht wirklich ausgeführt werden) die gleiche Gefahr wie für Destruktoren und passieren, wenn etwas im Konstruktor ausgelöst wird - der Compiler muss dann den Destruktor aufrufen der Mitglieder. This answer verwendet einige implizite Definition von Standardkonstruktoren.
Das gleiche gilt auch für Sichtbarkeit und PODness, die ich über den Destruktor oben gesagt habe.
Es gibt einen wichtigen Unterschied bei der Initialisierung. Wenn Sie einen vom Benutzer deklarierten Konstruktor setzen, erhält Ihr Typ keine Wertinitialisierung von Membern mehr, und es obliegt Ihrem Konstruktor, die erforderlichen Initialisierungen vorzunehmen. Beispiel:
struct A {
int a;
};
struct B {
int b;
B() { }
};
In diesem Fall ist die folgende immer wahr
assert(A().a == 0);
Während die folgende ist nicht definiertes Verhalten, weil b
nie initialisiert wurde (Konstruktor weggelassen, dass). Der Wert kann Null sein, kann aber auch ein anderer seltsamer Wert sein. Der Versuch, von einem solchen nicht initialisierten Objekt zu lesen, führt zu undefiniertem Verhalten.
assert(B().b == 0);
Dies gilt auch für die Verwendung dieser Syntax in new
, wie new A()
(beachten Sie die Klammern am Ende - wenn sie Wert Initialisierung weggelassen werden nicht durchgeführt wird, und da es keine Benutzer erklärt Konstruktor, der es nicht initialisieren , a
wird nicht initialisiert).
Ich habe diese Frage ein wenig modifiziert, um die Nachbearbeitung zu einem eigentlichen Teil der Frage zu machen. Wenn es Syntaxfehler in den Teilen gibt, die ich bearbeitet habe, schreie mich an, nicht den ursprünglichen Fragesteller. @Andrew, wenn du das Gefühl hast, dass ich deine Frage zu sehr geändert habe, kannst du sie gerne rückgängig machen. Wenn du die Änderung magst, aber denkst, dass es nicht genug ist, kannst du natürlich deine eigene Frage bearbeiten. –