Destruktor selbst aufrufen



  • Hallo,
    wenn ich einen Klassendestruktor selbst aufrufe, z.b. MyClassInstance.~MyClass(), wird dann nur der Code innerhalb dieses Destruktors ausgeführt, oder wird auch der Speicher der Klasse freigegeben?
    Oder anders formuliert: Kann ich danach die Klasse weiterhin benutzen?
    MfG,
    Max


  • Administrator

    Es wird nur der Code ausgeführt und wenn du in diesem Code Speicher freigibst, wird dieser halt freigegeben. Aber der Speicher des Objekts selbst bleibt erhalten, das Objekt wird also nicht zerstört.
    Nur eines sollte man sagen, dass es im allgemeinen als schlechter Stil angesehen wird. Lieber eine Funktion einführen à la clear oder ähnliches. Die kann man ja dann auch aus dem Destruktor aufrufen.

    Grüssli



  • Dravere schrieb:

    Es wird nur der Code ausgeführt und wenn du in diesem Code Speicher freigibst, wird dieser halt freigegeben. Aber der Speicher des Objekts selbst bleibt erhalten, das Objekt wird also nicht zerstört.

    Das Objekt wird sehr wohl zerstört. Übrig bleibt lediglich leerer uninitialisierter Speicher von der Größe des zerstörten Objektes. Hin und wieder sieht man so grausige Codebeispiele wie

    class Foo {/*...*/};
    
    Foo f;
    Foo* pf = &f;
    f.~Foo(); //Destruktor
    new (f) Foo(); //placement new, Konstruktion an der Stelle des alten f
    

    gefolgt von der Behauptung, f sei das selbe Objekt wie vorher. Das ist es aber nicht, zwar mag es ein Objekt sein dass genauso aussieht wie f vorher und es liegt sicherlich am selben Platz im Speicher, aber nach Aufruf des Dtors ist die Objektlebenszeit beendet und der Aufruf des Konstruktors (hier via placement new) generiert ein neues Objekt.
    Den Destruktor sollte man wirklich nur dann manuell aufrufen, wenn es nicht anders geht und wenn man sehr genau weiß was man tut.
    Dazu siehe auch http://www.gotw.ca/gotw/022.htm und http://www.gotw.ca/gotw/023.htm



  • new (pf) Foo(); //placement new, Konstruktion an der Stelle des alten f
    

    😉


  • Administrator

    pumuckl schrieb:

    Das Objekt wird sehr wohl zerstört. Übrig bleibt lediglich leerer uninitialisierter Speicher von der Größe des zerstörten Objektes.

    Ehm, wie bitte? Ich dachte, dass der Destruktor wie eine Funktion ist. Nur das eben auch die Destruktoren der Basisklassen und Destruktor der Elemente aufgerufen wird. Der Speicher bleibt aber vorhanden, somit das Objekt auch.

    class Test
    {
    private:
      int m_a;
    
    public:
      Test()
        : m_a(5)
      { }
    
      ~Test() { };
    
    public:
      int get_a() const { return m_a; };
    };
    
    int main()
    {
      Test test;
      test.~Test();
    
      std::cout << test.get_a() << std::endl;
    
      return 0;
    }
    

    Willst du sagen, da es nicht garantiert ist, dass die Ausgabe dieses Programmes 5 ist? Das wäre mir nämlich ganz was neues. Aber man lernt ja auch nie aus.

    Grüssli



  • das verhalten sollte undefiniert sein. du hast mit "test.~Test();" das objekt zerstört, aber den speicher nicht freigegeben. hier darf dein compiler machen, was er will. zu beispiel auch die existenz deines objektes wegoptimieren.



  • ghorst schrieb:

    das verhalten sollte undefiniert sein. du hast mit "test.~Test();" das objekt zerstört, aber den speicher nicht freigegeben. hier darf dein compiler machen, was er will. zu beispiel auch die existenz deines objektes wegoptimieren.

    Es ist undefiniert.

    Siehe: ISO/IEC 14882 12.4/14



  • Dravere schrieb:

    Willst du sagen, da es nicht garantiert ist, dass die Ausgabe dieses Programmes 5 ist? Das wäre mir nämlich ganz was neues. Aber man lernt ja auch nie aus.

    ghorst schrieb:

    das verhalten sollte undefiniert sein.

    Genau das ist damit gemeint. Siehe auch die von mir gelinkten GOTW-Artikel. Zwischen dem Aufruf des Destruktors und dem folgenden placement new ist jeder Zugriff auf das Objekt undefiniert, da es schlichtweg kein Objekt mehr gibt, sondern nur rohen Speicher, schließlich heißt der Destruktor nicht umsonst so. Man muss eben zwischen dem Objekt selbst und dem Speicher, den es belegt unterscheiden.
    Btw: Wenn man wie im Beispiel automatische Variablen durch expliziten Dtor-Aufruf zerstört, muss man danach unbedingt wieder den Speicher per placement new mit einem Objekt des selben Typs belegen. Denn dem Compiler ist egal was du in der zwischenzeit machst, er ruft am Ende des Scopes den Destruktor auf - und ein Dtor für ein nichtexistentes Objekt aufzurufen ist genauso undefiniert wie jede normale Methode auch...


  • Administrator

    drakon schrieb:

    ghorst schrieb:

    das verhalten sollte undefiniert sein. du hast mit "test.~Test();" das objekt zerstört, aber den speicher nicht freigegeben. hier darf dein compiler machen, was er will. zu beispiel auch die existenz deines objektes wegoptimieren.

    Es ist undefiniert.

    Siehe: ISO/IEC 14882 12.4/14

    Werde ich jetzt nicht nachlesen, aber danke für die Info. Gut zu wiss... ehm ... nein, auf die Idee bin ich bisher noch nie gekommen, den Destruktor manuell aufzurufen. Das sieht schon nur deshalb scheusslich aus, weil meine Funktionen anders aussehen als die Klassennamen und somit der Destruktor anders aussieht als eine Methode.

    Gibt es eigentlich wirklich einen Grund, dass man den manuell aufrufen möchte? pumuckl hat von etwas geredet, wenn es nicht anders geht. Wann geht es denn eigentlich nicht anders? Objekte zerstört man ja eigentlich immer irgendwann irgendwie.

    Grüssli



  • Dravere schrieb:

    Gibt es eigentlich wirklich einen Grund, dass man den manuell aufrufen möchte? pumuckl hat von etwas geredet, wenn es nicht anders geht. Wann geht es denn eigentlich nicht anders? Objekte zerstört man ja eigentlich immer irgendwann irgendwie.

    bei nem eigenen speicher-management z.b.

    Meep Meep



  • @Dravere man kann das schon gebrauchen, bspw wenn man eine pool-allokator schreibt oder irgendeinen anderen allokator, da in dem die schritte destroy() und deallocate() getrennt sind. muss man natürlich nicht machen, man kann auch einen der beiden aufrufe zu einem dummy-aufruf machen.



  • Dravere schrieb:

    Werde ich jetzt nicht nachlesen...
    Gibt es eigentlich wirklich einen Grund, dass man den manuell aufrufen möchte? pumuckl hat von etwas geredet, wenn es nicht anders geht. Wann geht es denn eigentlich nicht anders? Objekte zerstört man ja eigentlich immer irgendwann irgendwie.

    Solltest du aber. 🙂 - Dort steht auch, wo es Sinn macht. 😉



  • Dravere schrieb:

    Gibt es eigentlich wirklich einen Grund, dass man den manuell aufrufen möchte?

    Ja, wenn man z.B. beim Zerstören von vielen Objekten vermeiden will, jedesmal ein delete aufzurufen. std::vector benutzt die Strategie in vielen Implementationen: es wird roher Speicher allokiert und erst beim push_back werden die Objetke darauf konsturiert. Bei einem pop_back oder ähnlichen Funktionen wird der Destruktor explizit aufgerufen ohne den Speicher wieder freizugeben. Daher auch die verschiedenen Konzepte capacity() vs. size() - ersteres gibt den gesamten Speicher (Roh und belegt) an, letzteres nur die anzahl der tatsächlich exisiterenden Objekte.


  • Administrator

    drakon schrieb:

    Solltest du aber. 🙂 - Dort steht auch, wo es Sinn macht. 😉

    Dann müsste ich zuerst einen Standard finden, wo ich es nachlesen kann. Habe immer noch keinen gekauft und immer noch nicht im Inet gesucht.

    @Meep Meep,
    Eigenes Speichermanagement mache ich ja schon über new/delete new[]/delete[] . Oder meinst du auch Pool-Allokatoren?

    @ghorst,
    Würden eigentlich Pool-Allokatoren nicht über Placement new/delete gehen? Ruft ein placement delete nicht automatisch den Destruktor auf? Habe noch nie placement new/delete verwendet, geschweige denn Pool-Allokatoren.

    Grüssli



  • es gibt kein placement delete.

    und new/delete bringen dir in vielen situationen nichts. versuch einfach nur mal einen std::vector zu implementieren. das hast du schnell geschafft und dann verstehst du den Sinn von placement new und dtor-calls.



  • Dravere schrieb:

    pumuckl schrieb:

    Das Objekt wird sehr wohl zerstört. Übrig bleibt lediglich leerer uninitialisierter Speicher von der Größe des zerstörten Objektes.

    Ehm, wie bitte? Ich dachte, dass der Destruktor wie eine Funktion ist. Nur das eben auch die Destruktoren der Basisklassen und Destruktor der Elemente aufgerufen wird. Der Speicher bleibt aber vorhanden, somit das Objekt auch.

    class Test
    {
    private:
      int m_a;
    
    public:
      Test()
        : m_a(5)
      { }
    
      ~Test() { };
    
    public:
      int get_a() const { return m_a; };
    };
    
    int main()
    {
      Test test;
      test.~Test();
    
      std::cout << test.get_a() << std::endl;
    
      return 0;
    }
    

    Willst du sagen, da es nicht garantiert ist, dass die Ausgabe dieses Programmes 5 ist? Das wäre mir nämlich ganz was neues. Aber man lernt ja auch nie aus.

    Grüssli

    Mach das mal, nachdem Du Test von einer anderen Klasse abgeleitet hast. Mit einem cout im Destruktor der Basisklasse siehst Du dann, daß auch dieser Konstruktor aufgerufen wird.


  • Administrator

    Shade Of Mine schrieb:

    es gibt kein placement delete.

    Ach, gibt es nicht? Ja, dann ist alles klar 😃
    Wie gesagt, habe noch nie damit gearbeitet. Nur einmal theoretisch knapp und kurz damit in Berührung gekommen.

    @Belli,
    Darum ging es aber nicht ...

    Grüssli



  • @shade of mine schau mal in stroustrup buch oder in den c++-standard. dort wird ein standardallokator angegeben, der new/delete nutzt, wobei nicht definiert ist, wann die aufgerufen werden.

    Dravere schrieb:

    @ghorst,
    Würden eigentlich Pool-Allokatoren nicht über Placement new/delete gehen? Ruft ein placement delete nicht automatisch den Destruktor auf? Habe noch nie placement new/delete verwendet, geschweige denn Pool-Allokatoren.

    zuerst einmal shade of mine hat recht: es gibt kein placement delete.
    delete ruft immer den destruktor auf. das ist richtig. aber ein allokator trennt das. die container in der stl verwenden allokatoren und nicht new/delete.

    Dravere schrieb:

    Wie gesagt, habe noch nie damit gearbeitet. Nur einmal theoretisch knapp und kurz damit in Berührung gekommen.

    das placement bezieht sich auf die möglichkeit einen neuen speicherbereich auf der stelle eines alten zu nutzen. das geht daher nur bei new.



  • ghorst schrieb:

    @shade of mine schau mal in stroustrup buch oder in den c++-standard. dort wird ein standardallokator angegeben, der new/delete nutzt, wobei nicht definiert ist, wann die aufgerufen werden.

    das würde mich ja mal echt interessieren. ist IMHO nämlich nicht möglich für einen standardkonformen allocator. zeig mal code her.



  • @shade of mine code ist im standard nicht aufgeführt. dort heißt es nur in 20.4.1.1 allocator - allocator members [lib.allocator.members]
    "pointer allocate(size_type n, allocator<void>::const_pointer hint=0);
    -3- Notes: Uses ::operator new(size_t) (lib.new.delete)."
    edit: "
    -6- Note: the storage is obtained by calling ::operator new(size_t), but it is unspecified when or how often this function is called. The use of hint is unspecified, but intended as an aid to locality if an implementation so desires."
    analog ist die aussage für deallocate.

    in stroustrups buch werden allocate() und deallocate() für den standardallokator nicht implementiert und für construct und destroy folgende implementierungen angegeben:

    void construct (pointer p, const T& wert) {new (p) T(wert);}
    void destruct (pointer p){p->~T();}
    

Anmelden zum Antworten