Destruktor selbst aufrufen



  • 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();}
    


  • Dravere schrieb:

    Shade Of Mine schrieb:

    es gibt kein placement delete.

    Ach, gibt es nicht? Ja, dann ist alles klar 😃

    Expliziter Dtor-Aufruf ist im Grunde der Counterpart zum placement-new. Wenn du irgendwo ein Objekt mit placement-new konstruierst solltest du es in den meisten (bzw. allen, mir fällt kein Gegenbeispiel ein) Fällen mit explizitem Dtor-Aufruf wieder zerstören. Den Speicher deallokierst du dann genauso wie du ihn vorher allokiert hast (z.B. als char-Array, mit free() zum malloc() usw.) Ist wie mit allen anderen Speicherbeschaffungsmaßnahmen: immer das selbe deallokations/zerstörungsverfahren anwenden wie bei der allokation/konstruktion:

    Automatische Variable -> automatischer Dtoraufruf
    new -> delete
    new[] -> delete[]
    placement-new (kein Speicher angefordert) -> expliziter Dtor-Aufruf (kein Speicher freigegeben)
    malloc -> free
    irgendeinAllocator::allocate() -> irgendeinAllocator::deallocate()

    ganz einfach eigentlich 😉
    Aus dem Grund ists auch so gefährlich, bei automatischen Variablen einen expliziten Dtoraufruf zu machen: man zerstört das Objekt anders als man es geschaffen hat. Man kanns in dem Fall eventuell wieder gutmachen, aber eben nur eventuell. Gerade bei Funktionen, in denen man das Objekt nur als Referenz oder Pointer übergeben bekommt, sollte man die Finger auf jeden Fall vom Dtor lassen - schließlich weiß man da nicht genau wie es erzeugt wurde und kann deshalb auch nicht wissen, was man mit der falschen Zerstörung alles kaputt macht (splicing etc, siehe GOTW-Artikel).



  • ghorst schrieb:

    @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)."

    Ich habe aber nie von operator new gesprochen sondern nur vom new operator...



  • da man ::new() nie außerhalb von new() aufrufen sollten, sind die beiden aussagen gleichwertig.



  • ghorst schrieb:

    da man ::new() nie außerhalb von new() aufrufen sollten, sind die beiden aussagen gleichwertig.

    Ich will auch nie ::new aufrufen. Habe ich noch nie gemacht.

    Kennst du den Unterschied zwischen operator new() und dem new operator?
    Sprich:
    void* p = ::operator new(sizeof(T));
    T* p = new T();

    ?



  • es gibt keinen. new() ruft entweder die standardimplementierung auf oder, wenn eine klassenspezifische gegeben ist, den operator ::new(), der sich dann um die vollständige speicherverwaltung kümmert.
    wenn ::new() nicht definiert ist, weiß ich offengestanden nicht, was passiert, wenn man den aufruft. daher ist "keinen" dahingehend mit vorbehalt zu verstehen.


Anmelden zum Antworten