Frage

Angenommen, wir haben ein (Spielzeug) C ++ Klasse wie folgt:

class Foo {
    public:
        Foo();
    private:
        int t;
};

Da kein destructor definiert ist, ein C ++ Compiler sollte man automatisch für die Klasse Foo erstellen. Wenn der destructor benötigt keine dynamisch zugewiesenen Speicher zu bereinigen (das heißt, konnten wir einigermaßen auf dem destructor vertrauen die Compiler uns gibt), wird eine leere destructor definieren, das heißt.

Foo::~Foo() { }

das gleiche tun wie der Compiler generierte ein? Was ist mit einem leeren Konstruktor - das heißt, Foo::Foo() { }

?

Wenn es Unterschiede gibt, wo gibt es sie? Wenn nicht, wird ein Verfahren gegenüber dem anderen bevorzugt?

War es hilfreich?

Lösung

Es wird das gleiche tun (nichts im Wesentlichen). Aber es ist nicht das gleiche wie wenn Sie es nicht geschrieben hat. Da die destructor Schreiben wird eine funktionierende Basis Klassendestruktor erfordern. Wenn die Basisklasse destructor ist privat oder wenn es ein anderer Grund ist es nicht aufgerufen werden kann, dann wird Ihr Programm ist fehlerhaft. Betrachten Sie diese

struct A { private: ~A(); };
struct B : A { }; 

Das ist in Ordnung, 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 Sie erstellen nie ein Objekt davon in erster Linie. Wenn Sie das tun, dann wird der Compiler eine entsprechende Diagnose angezeigt werden soll. Nun, wenn Sie bieten eine explizit

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, dass an das Mitglied Destruktoren um die Definition der destructor und implizite Anrufe Mitte. Betrachten Sie dieses Smart-Pointer-Mitglied

struct C;
struct A {
    auto_ptr<C> a;
    A();
};

Nehmen wir an, das Objekt vom Typ C wird in der Definition von A Konstruktor in der .cpp-Datei erstellt, die auch die Definition der Struktur C enthält. Nun, wenn Sie struct A verwenden und erfordern Zerstörung eines A Objekt, wird der Compiler eine implizite Definition des destructor bieten, genau wie im Fall oben. Das destructor ruft auch implizit den destructor des auto_ptr-Objekts. Und das wird löschen Sie den Zeiger es hält, die das C Objekt zeigt - ohne die Definition von C zu wissen! Das erschien in der .cpp Datei, in der Konstrukteurs-Struktur A definiert ist.

Dies ist eigentlich ein häufiges Problem, das Pimpl Idioms bei der Umsetzung. Die Lösung hier ist eine destructor hinzuzufügen und eine leere Definition in der .cpp Datei zur Verfügung stellen, in dem die Struktur C definiert ist. In der Zeit, die die destructor seiner Mitglieds aufruft, wird es dann wissen die Definition von struct C und seine destructor korrekt aufrufen kann.

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 der aktuellen C ++ macht ist, wenn Sie memset und Freunde auf ein solches Objekt verwendet werden soll, die einen Benutzer erklärt destructor hat. Solche Typen sind nicht PODs mehr (nur alte Daten), und diese werden nicht Bit kopiert werden dürfen. Beachten Sie, dass diese Einschränkung nicht wirklich benötigt - und die nächste C ++ Version hat sich die Situation auf diese verbessert, so dass es Ihnen erlaubt, noch Bit Kopie solcher Typen, solange andere wichtige Änderungen werden nicht gemacht.


Da Sie für Konstrukteure gefragt: Nun, für diese sehr die gleichen Dinge wahr sind. auch implizite Anrufe Destruktoren enthalten Beachten Sie, dass Konstrukteure. Auf Dinge wie auto_ptr, diese Anrufe (auch wenn sie nicht tatsächlich zur Laufzeit geschehen - die reine Möglichkeit bereits hier zählt) den gleichen Schaden wie für Destruktoren tun, und passieren, wenn etwas im Konstruktor wirft - der Compiler benötigt wird dann den destructor anrufen der Mitglieder. Diese Antwort einige macht Verwendung von impliziter Definition des Standardkonstruktoren.

Auch ist das gleiche gilt für die Sichtbarkeit und PODness, die ich oben über die destructor sagte.

Es gibt einen wichtigen Unterschied in Bezug auf Initialisierung. Wenn Sie einen Benutzer erklärt Konstruktor setzen, wird Ihre Art nicht Wert Initialisierung der Mitglieder mehr erhalten, und es ist bis zu Ihrem Konstruktor Initialisierungen zu tun, die benötigt wird. Beispiel:

struct A {
    int a;
};

struct B {
    int b;
    B() { }
};

In diesem Fall wird die folgende ist immer wahr

assert(A().a == 0);

Während die folgend undefiniertes Verhalten ist, weil b nie initialisiert wurde (Konstruktor weggelassen, dass). Der Wert kann Null sein, aber kann aswell andere seltsame Wert sein. Der Versuch, aus einer solchen nicht initialisierten Objekt verursacht nicht definiertes Verhalten zu lesen.

assert(B().b == 0);

Dies gilt auch für diese Syntax in new verwenden, wie new A() (beachten Sie die Klammern am Ende - wenn sie Wert Initialisierung weggelassen werden nicht gemacht wird, und da es keine Benutzer Konstruktor erklärt, dass es nicht initialisieren, wird a sein links nicht initialisierten).

Andere Tipps

Ich weiß, ich bin spät in der Diskussion, doch sagt meine Erfahrung, dass der Compiler anders verhält, wenn eine leere destructor mit Blick auf im Vergleich zu einem Compiler eine generiert. Zumindest ist dies der Fall mit MSVC ++ 8.0 (2005) und MSVC ++ 9.0 (2008).

Wenn bei der generierten Assembly für einige Code unter Verwendung von Ausdrucksvorlagen suchen, wurde mir klar, dass im Release-Modus wurde der Aufruf an meine BinaryVectorExpression operator + (const Vector& lhs, const Vector& rhs) nie inline. (Bitte nicht achten auf die genauen Typen und Unterschrifts).

Um das Problem zu diagnostizieren, aktivierte ich die verschiedenen Compiler Warnungen, die ausgeschaltet sind durch Standard . Die C4714 Warnung ist besonders interessant. Es wird vom Compiler emittiert wird, wenn eine Funktion mit __forceinline markiert nicht dennoch nicht erhalten inlined .

aktivierte ich die C4714 Warnung und ich den Bediener mit __forceinline markiert, und ich kann die Compiler-Berichte war es nicht in der Lage zu inline den Anruf an die Bediener überprüfen.

Unter den Gründen, in der Dokumentation beschrieben, schlägt der Compiler eine Funktion mit __forceinline markiert Inline für:

  

Funktionen ein abwickelbar Objekt nach Wert zurückkehrt, wenn -GX / EHs / EHa auf

ist

Dies ist der Fall meiner BinaryVectorExpression operator + (const Vector& lhs, const Vector& rhs). BinaryVectorExpression wird durch Wert zurückgegeben und obwohl sein destructor leer ist, macht es diesen Rückgabewert als abwickelbar Objekt betrachtet wird. Hinzufügen throw () zum destructor half nicht, den Compiler und ich vermeiden sowieso Ausnahme Spezifikationen mit . Kommentiert aus dem leeren destructor lassen Sie den Compiler vollständig den Code inline.

Der take-away ist, dass ab jetzt, in jeder Klasse, schreibe ich die Menschen leer Destruktoren auf Kommentar zu informieren, der destructor tut nichts absichtlich die gleiche Art, wie Menschen die leere Ausnahme Spezifikation Kommentar aus `/ * throw () * / um anzuzeigen, dass der Destruktor nicht werfen kann.

//~Foo() /* throw() */ {}

Ich hoffe, das hilft.

Der leere destructor, die Sie aus der Klasse definiert hat ähnliche Semantik in den meisten Grüßen, aber nicht in allen.

Insbesondere die implizit definiert destructor
1) ist ein Inline öffentliches Mitglied (Sie ist nicht inline)
2) bezeichnet als trivial destructor (notwendig trivial Typen zu machen, die in Gewerkschaften sein können, sie nicht)
3) eine Ausnahmespezifikation (throw (), Ihnen nicht der Fall)

Ja, das leer destructor ist das gleiche wie die automatisch generierte ein. Ich habe lasse immer nur die Compiler automatisch erzeugen; Ich glaube nicht, es notwendig ist, den Destruktor explizit angeben, wenn Sie etwas Ungewöhnliches tun müssen. Macht es virtuell oder privat, sagt

Ich stimme mit David außer, dass ich würde sagen, es ist in der Regel eine gute Praxis, einen virtuellen Destruktor d.h zu definieren.

virtual ~Foo() { }

virtuelle destructor verpasst kann Speicherverlust führen, weil Menschen, die von Ihrer Foo-Klasse erben können nicht bemerkt, dass ihr destructor wird nie aufgerufen werden !!

Ich würde sagen, am besten die leere Erklärung zu setzen, ist es keine Zukunft Maintainer sagt, dass es nicht um ein Versehen ist, und du hat wirklich bedeuten den Standard zu verwenden.

Eine leere Definition ist in Ordnung, da die Definition verwiesen werden kann

virtual ~GameManager() { };
Die leere Erklärung täuschend ähnlich ist in appearance
virtual ~GameManager();
noch die gefürchteten lädt keine Definition für virtuellen destructor Fehler
Undefined symbols:
  "vtable for GameManager", referenced from:
      __ZTV11GameManager$non_lazy_ptr in GameManager.o
      __ZTV11GameManager$non_lazy_ptr in Main.o
ld: symbol(s) not found

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top