2016-02-17 4 views
46

Haftungsausschluss: Ich weiß, das ist schlechtes Design, ich stelle einfach die Frage aus Neugierde, um tieferes Wissen darüber zu erlangen, wie der Destruktor in C++ funktioniert.Revive-Objekt von Destruktor in C++?

In C# man schreiben kann: GC.KeepAlive(this) im Destruktor einer Klasse (siehe bearbeiten unten), und das würde bedeuten, dass das Objekt noch in Erinnerung lebendig sein wird, selbst nachdem der Destruktoraufrufs abgeschlossen ist.

Ermöglicht das Design von C++ das Wiederbeleben eines Objekts aus dem Destruktor ähnlich dem oben beschriebenen C# -Algorithmus?

bearbeiten: Wie unten durch eine Antwort darauf hingewiesen wird GC.ReRegisterForFinalize() näher auf die Frage als GC.KeepAlive(this) bezogen.

+0

einfach den destructor leer lassen. – becko

+1

@becko Wie würde das funktionieren? Wenn alle Typen in der Klasse POD oder RAII sind, dann ist alles ein leerer Destruktor. – NathanOliver

+0

Das würde immer noch dazu führen, dass das Objekt gelöscht wird. –

Antwort

99

Die kurze Antwort ist: nein. C++ verwendet keine Speicherbereinigung wie Java oder C#. Wenn ein Objekt zerstört wird, wird es sofort zerstört. Für immer gegangen. Vereinte den Chor unsichtbar. Pining für die Fjorde, etc ...

Und dies mit anderen Worten oft über ein paar zu sagen, so dass es keine mögliche weasily Umdeutung ist ...

Der destructor als Teil des Objekt Zerstörung aufgerufen wird. Die Objektzerstörung besteht darin, den Destruktor aufzurufen und den Speicher freizugeben, der für das Objekt selbst verwendet wurde. Es ist ein einzelner Prozess, nicht zwei separate Prozesse. Während der Destruktor ausgeführt wird, ist das Objekt für den Destruktor weiterhin vorhanden, aber es existiert nach einer geborgten Zeit. Es ist eine ausgemachte Sache, dass das Objekt verdampft wird, sobald der Destruktor zurückkehrt. Sobald ein Destruktor aufgerufen wird, wird das Objekt zerstört und nichts wird sein Schicksal ändern.

Verstehen Sie dies: Der Grund, warum ein Destruktor aufgerufen wird, ist entweder: das Objekt wurde ursprünglich auf dem Haufen mit "neu" zugewiesen, und es ist jetzt "löschen" d. "löschen" bedeutet "löschen", nicht "löschen vielleicht". Also wird das Objekt gelöscht. Wenn das Objekt im Stapel zugeordnet wurde, wurde der Bereich vom Ausführungsthread verlassen, sodass alle im Bereich deklarierten Objekte zerstört werden. Der Destruktor wird technisch aufgerufen, weil das Objekt zerstört wird. Also wird das Objekt zerstört. Das Ende.

C++ erlaubt Ihnen, einen benutzerdefinierten Zuordner für Ihre Klassen zu implementieren. Wenn Ihnen danach ist, können Sie Ihre eigenen benutzerdefinierten Speicherzuweisungs- und Freigabe-Funktionen schreiben, die die von Ihnen gewünschte Funktionalität implementieren. Obwohl diese niemals für zugeordnete Stapelobjekte (d. H. Lokale Variablen) verwendet werden.

+1

Also wird der Destruktor * genannt * nachdem * das Objekt zerstört ist? –

+32

Der Destruktor wird als Teil der Objektzerstörung aufgerufen. Die Objektzerstörung besteht darin, den Destruktor aufzurufen und den Speicher freizugeben, der für das Objekt selbst verwendet wurde. Es ist ein einzelner Prozess, nicht zwei separate Prozesse. Während der Destruktor ausgeführt wird, ist das Objekt für den Destruktor weiterhin vorhanden, aber es existiert nach einer geborgten Zeit. Es ist eine ausgemachte Sache, dass das Objekt verdampft wird, sobald der Destruktor zurückkehrt. Sobald ein Destruktor aufgerufen wird, wird das Objekt zerstört und nichts wird sein Schicksal ändern. –

+0

Aber wenn Sie eine Zuweisung eines Zeigers von 'this' zu einem Objekt tun, würde das es am Leben erhalten? –

52

Sie sind eigentlich irreführend, was GC.KeepAlive in .NET tut. Es sollte nicht in einem Destruktor eines Objekts verwendet werden, um zu verhindern, dass dieses Objekt zerstört wird - tatsächlich ist GC.KeepAlive() leer und hat keine Implementierung. Siehe den .NET-Quellcode here.

Es stellt sicher, dass das als Parameter übergebene Objekt nicht Garbage Collection vor der Aufruf an GC.KeepAlive passiert. Das Objekt, das an KeepAlive als Parameter übergeben wird, kann direkt nach dem Aufruf von GC.KeepAlive als Garbage Collection-Objekt erfasst werden. Da KeepAlive keine eigentliche Implementierung hat, geschieht dies rein aufgrund der Tatsache, dass der Compiler einen Verweis auf das Objekt, das als Parameter an KeepAlive übergeben werden soll, beibehalten muss. Jede andere Funktion (die nicht vom Compiler oder der Laufzeit inlined ist), die das Objekt als einen Parameter verwendet, könnte stattdessen ebenfalls verwendet werden.

+0

Was passiert, wenn das Objekt in einem anderen Thread am Leben erhalten wird? –

+6

In .net-Objekten werden am Leben gehalten (verhindert, dass Müll gesammelt wird), indem sie auf sie verweisen. Threads spielen hier keine Rolle. – NineBerry

+1

Ein Objekt wird am Leben erhalten, wenn es in einem anderen Thread referenziert wird (dh lebendig gehalten wird) –

4

Das ist in keiner Sprache möglich.

Ihr Verständnis ist ein bisschen aus. GC.KeepAlive markiert das Objekt als nicht sammelbar durch den Garbage Collector. Dadurch wird verhindert, dass die Garbage-Collection-Strategie das Objekt zerstört, und es ist nützlich, wenn das Objekt in nicht verwaltetem Code verwendet wird, in dem der Garbage Collector die Verwendung nicht verfolgen kann. Dies bedeutet nicht, dass das Objekt nach der Zerstörung im Speicher ist.

Sobald ein Objekt die Zerstörung beginnt, wird der Code Ressourcen freigeben (Speicher, Dateihandler, Netzwerkverbindungen). Die Reihenfolge ist normalerweise von der tiefsten abgeleiteten Klasse zurück zur Basisklasse. Wenn etwas in der Mitte die Zerstörung verhindern sollte, gibt es keine Garantie, dass diese Ressourcen wieder erlangt werden könnten und das Objekt in einem inkonsistenten Zustand wäre.

Was Sie wollen, ist wahrscheinlich eine std::shared_ptr haben, die Kopien und Referenzen verfolgt und nur das Objekt zerstört, sobald niemand es mehr braucht.

+4

Beachten Sie meine Antwort, dass GC.KeepAlive() das Objekt vor dem Aufrufen von KeepAlive() nur davor schützt, Müll gesammelt zu werden, nicht danach. – NineBerry

+4

Ihr Verständnis von 'GC.KeepAlive' ist falsch. Außerdem muss ein Objekt für die Verwendung in nicht verwaltetem Code * angeheftet * werden, sodass es sowieso nicht erfasst werden kann. –

8

Hier ist eine Idee:

C* gPhoenix= nullptr; 

C::~C() 
{ 
gPhoenix= new C (*this); // note: loses any further-derived class ("slice") 
} 

Wenn nun die beteiligten Objekte (Basen oder Mitglieder) wirklich Destruktoren haben, die etwas tun, das zu einem Problem führt, wenn Sie delete gPhoenix; so werden Sie aufwendigere Mechanismen benötigen je auf was es wirklich versucht zu erreichen. Aber du hast keine wirklichen Ziele, nur neugierige Erkundungen, also sollte das darauf hindeuten.

Wenn der Körper des Destruktors aufgerufen wird, ist das Objekt immer noch vollkommen gut. Es scheint vollkommen wichtig und normal zu sein, wenn Sie normale Aufrufe von Mitgliedsfunktionen innerhalb des Destruktors ausführen.

Der Speicher, der das Objekt besitzt, wird zurückgewonnen, so dass Sie nicht an Ort und Stelle bleiben können. Und nachdem der Körper verlassen wurde, findet eine andere Zerstörung automatisch statt und kann nicht gestört werden. Sie können das Objekt jedoch duplizieren, bevor dies geschieht.

+1

Eine Sache zu beachten ist, dass alle Unterklasse-Destruktoren bereits aufgerufen wurden, wenn Objekt tatsächlich eine Instanz einer Unterklasse war. In diesem Fall können die Dinge haarig werden, abhängig davon, was die Unterklasse getan hat. Im besten Fall erhalten Sie eine gültige Kopie der Instanz der gerade zerstörten Klasse und verlieren einfach, was auch immer die Unterklasse hinzugefügt hat. – hyde

+0

@Deduplicator Destruktor Aufruf Reihenfolge ist von Unterklasse zu Oberklasse, umgekehrt von Konstruktor Aufruf Reihenfolge (ganz natürlich, wenn Sie darüber nachdenken, Unterklasse Sachen hängt von gültigen Superklasse Zeug darunter). Also ich denke ich habe oben richtig geschrieben. – hyde

+0

@hyde Ich vertausche immer Superklasse und Unterklasse. Wahrscheinlich, weil die Oberklasse ein Unterobjekt ist. – Deduplicator

6

Wie hat already been pointed out, GC.KeepAlive nicht das tun.

Solange .NET geht, ist es möglich, von der Finalizerthread wiederzubeleben GC.ReRegisterForFinalize verwenden, können Sie noch einen Verweis darauf, wenn man einen WeakReference oder GCHandle Tracking ressurection haben, oder einfach nur this außerhalb der Klasse zu etwas geben. Dadurch wird die Zerstörung abgebrochen.

Das ist ein alter Trick, um Garbage Collection in .NET 2.0 no longer relevant zu erkennen, aber funktioniert immer noch (irgendwie, Garbage Collection kann jetzt teilweise sein, und parallel mit anderen Threads).

Emphase sollte auf die Tatsache gesetzt werden, dass auf .NET Sie einen Finalizer verwenden, der vor der Zerstörung läuft, und es verhindern kann. Obwohl es technisch korrekt ist, dass Sie ein Objekt nach der Zerstörung nicht wiederherstellen können - in any language - können Sie sich dem Verhalten nähern, das Sie in .NET beschreiben, außer dass Sie stattdessen GC.ReRegisterForFinalize verwenden.


auf C++, haben Sie bereits die correct answer gegeben worden.

3

Falls es hilft, sind die Destruktor-Funktion und die Speicherzuweisung unterschiedlich.

Der Destruktor ist nur eine Funktion.Sie können es explizit aufrufen. Wenn es nichts destruktives tut, dann ist es nicht unbedingt problematisch, es erneut aufzurufen (z. B. wenn das Objekt den Geltungsbereich verlässt oder gelöscht wird), obwohl es sehr seltsam wäre; möglicherweise gibt es einen Abschnitt, der dies in der Norm behandelt. Siehe Beispiel unten. Zum Beispiel rufen einige STL-Container explizit den Destruktor auf, da sie die Objektlebensdauern und die Speicherzuordnung getrennt verwalten.

In der Regel wird der Compiler Code einfügen, um den Destruktor aufzurufen, wenn eine automatische Variable den Gültigkeitsbereich verlässt oder ein Heap-allokiertes Objekt mit delete zerstört wird. Diese Aufhebung der Speicherzuordnung kann nicht im Destruktor manipuliert werden.

Sie können die Speicherzuordnung übernehmen, indem Sie zusätzliche Implementierungen des neuen Operators bereitstellen oder vorhandene wie "placement new" verwenden. Das allgemeine Standardverhalten ist jedoch, dass der Compiler Ihren Destruktor aufruft. Die Tatsache, dass später Speicher gelöscht wird, liegt außerhalb der Kontrolle des Destruktors.

#include <iostream> 
#include <iomanip> 

namespace test 
{ 
    class GotNormalDestructor 
    { 
    public: 
     ~GotNormalDestructor() { std::wcout << L"~GotNormalDestructor(). this=0x" << std::hex << this << L"\n"; } 
    }; 

    class GotVirtualDestructor 
    { 
    public: 
     virtual ~GotVirtualDestructor() { std::wcout << L"~GotVirtualDestructor(). this=0x" << std::hex << this << L"\n"; } 
    }; 

    template <typename T> 
    static void create_destruct_delete(wchar_t const name[]) 
    { 
    std::wcout << L"create_destruct_delete<" << name << L">()\n"; 
    { 
     T t; 
     std::wcout << L"Destructing auto " << name << L" explicitly.\n"; 
     t.~T(); 
     std::wcout << L"Finished destructing " << name << L" explicitly.\n"; 
     std::wcout << name << L" going out of scope.\n"; 
    } 
    std::wcout << L"Finished " << name << L" going out of scope.\n"; 
    std::wcout << L"\n"; 
    } 

    template <typename T> 
    static void new_destruct_delete(wchar_t const name[]) 
    { 
    std::wcout << L"new_destruct_delete<" << name << L">()\n"; 
    T *t = new T; 
    std::wcout << L"Destructing new " << name << L" explicitly.\n"; 
    t->~T(); 
    std::wcout << L"Finished destructing new " << name << L" explicitly.\n"; 
    std::wcout << L"Deleting " << name << L".\n"; 
    delete t; 
    std::wcout << L"Finished deleting " << name << L".\n"; 
    std::wcout << L"\n"; 
    } 

    static void test_destructor() 
    { 
    { 
     std::wcout << L"\n===auto normal destructor variable===\n"; 
     GotNormalDestructor got_normal; 
    } 

    { 
     std::wcout << L"\n===auto virtual destructor variable===\n"; 
     GotVirtualDestructor got_virtual; 
    } 

    { 
     std::wcout << L"\n===new variables===\n"; 
     new_destruct_delete<GotNormalDestructor>(L"GotNormalDestructor"); 
     new_destruct_delete<GotVirtualDestructor>(L"GotVirtualDestructor"); 
    } 

    { 
     std::wcout << L"\n===auto variables===\n"; 
     create_destruct_delete<GotNormalDestructor>(L"GotNormalDestructor"); 
     create_destruct_delete<GotVirtualDestructor>(L"GotVirtualDestructor"); 
    } 

    std::wcout << std::endl; 
    } 
} 

int main(int argc, char *argv[]) 
{ 
    test::test_destructor(); 

    return 0; 
} 

Beispielausgabe

===auto normal destructor variable=== 
~GotNormalDestructor(). this=0x0x23fe1f 

===auto virtual destructor variable=== 
~GotVirtualDestructor(). this=0x0x23fe10 

===new variables=== 
new_destruct_delete<GotNormalDestructor>() 
Destructing new GotNormalDestructor explicitly. 
~GotNormalDestructor(). this=0x0x526700 
Finished destructing new GotNormalDestructor explicitly. 
Deleting GotNormalDestructor. 
~GotNormalDestructor(). this=0x0x526700 
Finished deleting GotNormalDestructor. 

new_destruct_delete<GotVirtualDestructor>() 
Destructing new GotVirtualDestructor explicitly. 
~GotVirtualDestructor(). this=0x0x526700 
Finished destructing new GotVirtualDestructor explicitly. 
Deleting GotVirtualDestructor. 
~GotVirtualDestructor(). this=0x0x526700 
Finished deleting GotVirtualDestructor. 


===auto variables=== 
create_destruct_delete<GotNormalDestructor>() 
Destructing auto GotNormalDestructor explicitly. 
~GotNormalDestructor(). this=0x0x23fdcf 
Finished destructing GotNormalDestructor explicitly. 
GotNormalDestructor going out of scope. 
~GotNormalDestructor(). this=0x0x23fdcf 
Finished GotNormalDestructor going out of scope. 

create_destruct_delete<GotVirtualDestructor>() 
Destructing auto GotVirtualDestructor explicitly. 
~GotVirtualDestructor(). this=0x0x23fdc0 
Finished destructing GotVirtualDestructor explicitly. 
GotVirtualDestructor going out of scope. 
~GotVirtualDestructor(). this=0x0x23fdc0 
Finished GotVirtualDestructor going out of scope.