Smart Pointer statt 'new' und 'delete'?


  • Mod

    Ich denke nicht, da das Objekt ja nicht konstruiert wurde (Member werden natuerlich ordnungsgemaess zertoert).

    Ja, natürlich.

    An object of any storage duration whose initialization [..] is terminated by an exception will have destructors executed for all of its fully constructed subobjects [..].
    Similarly, if the non-delegating constructor for an object has completed execution and a delegating constructor for that object exits with an exception, the object’s destructor will be invoked.
    If the object was allocated in a new-expression, the matching deallocation function [..] is called to free the storage occupied by the object.


  • Mod

    Da ist übrigens nach wie vor undefiniertes Verhalten, da du einen Destruktor für ein nicht existierendes Objekt aufrufst.



  • Arcoth schrieb:

    Da ist übrigens nach wie vor undefiniertes Verhalten, da du einen Destruktor für ein nicht existierendes Objekt aufrufst.

    Muss ich denn jedesmal nach der Argumentation fragen, also nach dem Standard, da du ihn ja grade sowieso zur Hand hast ... jaja, ich rufe clean() auf. 🙂

    if the non-delegating constructor for an object has completed execution and a delegating constructor for that object exits with an exception, the object’s destructor will be invoked.

    D.h. in dem Fall von bin_tree und C++11, dass man das Objekt mittels einfachem Konstruktor konstruieren kann und dann im Kopierkonstruktor sicher seine Exception werfen kann, d.h.

    bin_tree::bin_tree(bin_tree const &other): bin_tree() { // give me a valid object for deconstruction
        nodes.resize(other.nodes.size(),0) 
        for(unsigned int i = 0; i < nodes.size(); ++i) { 
            nodes[i] = new node(other.nodes[i]);        } 
        } 
    }
    

    es wuerde bei Fehlschlagen von resize oder new mittels Exception trotzdem der Destruktor aufgerufen werden.



  • @knivil: Was meinst du, passiert, wenn ich dann

    std::unique_ptr<bin_tree> p(new bin_tree(some_other_tree));
    

    schreibe und bin_trees Konstruktor schmeißt? Wer räumt den von operator new angeforderten Speicher wieder weg?


  • Mod

    Muss ich denn jedesmal nach der Argumentation fragen, also nach dem Standard

    .. ich finde kein bestätigendes Zitat, obwohl ich todsicher überzeugt bin, dass man im Konstruktorrumpf den Destruktor nicht aufrufen darf. Wenn das kein UB ist, dann fresse ich einen Besen. Ich finde aber einfach nichts... 😞

    Eins steht fest: Anscheinend "existiert" das Objekt, denn im Konstruktor kann man natürlich auf seine Member zugreifen. In 12.6 und 12.7 finde ich auch nichts.


  • Mod

    Wer räumt den von operator new angeforderten Speicher wieder weg?

    Na die Implementierung 😉

    §15.2/2 schrieb:

    If the object was allocated in a new-expression, the matching deallocation function (3.7.4.2, 5.3.4, 12.5), if any, is called to free the storage occupied by the object.



  • Ach, verdammt. Ja, du hast natürlich recht; ich hab mich da böse verdacht.

    Aber die Member des Objektes, die bereits konstruiert wurden, werden auf die Weise doppelt zerstört, oder? Koscher sieht das auf jeden Fall nicht aus.


  • Mod

    seldon schrieb:

    Aber die Member des Objektes, die bereits konstruiert wurden, werden auf die Weise doppelt zerstört, oder?

    Ja, richtig! Da ist ja der entscheidende Punkt, den ich gesucht habe. Einmal durch den expliziten Destruktoraufruf, und einmal durch das Verlassen des Konstruktors durch die Exception. 💡



  • Der Destruktor ist auch nur eine Funktion, wo ist das Problem?


  • Mod

    knivil schrieb:

    Der Destruktor ist auch nur eine Funktion, wo ist das Problem?

    Er ruft Basisklassen- und Member-Destruktoren auf. Außerdem wird er im Standard als die Funktion behandelt, die die Lebenszeit eines Objektes beendet, sogar wenn er nichts tut.



  • knivil schrieb:

    Der Destruktor ist auch nur eine Funktion, wo ist das Problem?

    Muss hier wirklich noch einmal undefiniertes Verhalten erklärt werden?
    Den Destruktor darf man genau dann aufrufen, wenn man zuvor das Objekt mit Placement new erfolgreich initialisiert hat.



  • @Arcoth: Danke!

    Recap: ptr_container, private Vererbung, mein grausamer Vorschlag mit clean(), delegating constructors.


  • Mod

    TyRoXx schrieb:

    Den Destruktor darf man genau dann aufrufen, wenn man zuvor das Objekt mit Placement new erfolgreich initialisiert hat.

    Ja, unter anderem.

    #include <iostream>
    
    int main()
    {
    	struct A
    	{
    		A() = default;
    		A( char const* str ) : str{str} {}
    		std::string str = "Hallo";
    	};
    
    	A a;
    	a.A::~A();
    	new (&a) A{"Hallihallo"};
    	std::cout << a.str;
    }
    

    Wohldefiniertes Verhalten.



  • Arcoth schrieb:

    Wohldefiniertes Verhalten.

    Stimmt, das hatte ich vergessen. Wenn aber new (&a) A{"Hallihallo"} in Zeile 14 wirft, ist es wieder undefiniert.



  • TyRoXx schrieb:

    Arcoth schrieb:

    Wohldefiniertes Verhalten.

    Stimmt, das hatte ich vergessen. Wenn aber new (&a) A{"Hallihallo"} in Zeile 14 wirft, ist es wieder undefiniert.

    Wieso?
    Wirft der Konstruktor, schlägt die Erzeugung des neuen Objektes fehl, das erste a Objekt, in dessen Speicher das neue zufällig erzeugt werden sollte ist aber erfolgreich erstellt und dessen Destruktor wird aufgerufen.
    Das einzige Problem, was ich mir vorstellen könnte, ist dass der Destruktor von A dann mit einem teilweise erzeugten Objekt zurecht kommen muss... Wartet mal, kann es sein, dass der Standard das aus diesem Grund explicit verbietet?



  • Nathan schrieb:

    Wartet mal, kann es sein, dass der Standard das aus diesem Grund explicit verbietet?

    Nachdem du einen Destruktor ausführst, gibt es das Objekt nicht mehr.
    Und Objekte die es nicht gibt darf man nicht zerstören.

    Also ja, der Standard verbietet das explizit.

    Nathan schrieb:

    Das einzige Problem, was ich mir vorstellen könnte, ist dass der Destruktor von A dann mit einem teilweise erzeugten Objekt zurecht kommen muss...

    Nö. Er müsste mit einem "vollständig zerstörten" Objekt klarkommen. (Anders gesagt: er würde auf ein nicht existierendes Objekt aufgerufen.)

    A a;
    // OK, "a" ist jetzt ein "A" Objekt
    a.A::~A();
    // OK, "a" ist jetzt eine Bytewurst, "A" Objekt gibt es im Moment nicht
    new (&a) A{"Hallihallo"}; // <--- Exception
    // Bevor die Exception aus "new" rausfliegt zerstört die Implementierung jetzt erstmal alle vollständig konstruierten Teilobjekte.
    // (Dann erfolgt noch der Aufruf der zum placement new passenden placement delete Funktion,
    //  die in diesem Fall einfach nix tut. Tut aber hier eigentlich nix zur Sache.)
    // D.h. "a" ist hier wieder nur eine Bytewurst, "A" Objekt gibt es keines
    
    // Jetzt "verlässt" die Exception den "new" Ausdruck.
    
    // --> Unwinding -> Scope-Exit
    
    // Und ich behaupte mal dass beim Scope-Exit wieder "a.A::~A();" aufgerufen wird, weil die Implementierung vermutlich davon ausgehen kann,
    // dass man solche komischen Dinge einfach nicht macht.
    // Und der Aufruf "a.A::~A();" während des Scope-Exit ist UB, weil auf ein Objekt zugegriffen wird, welches gar nicht existiert.
    


  • hustbaer schrieb:

    A a;
    // OK, "a" ist jetzt ein "A" Objekt
    a.A::~A();
    // OK, "a" ist jetzt eine Bytewurst, "A" Objekt gibt es im Moment nicht
    new (&a) A{"Hallihallo"}; // <--- Exception
    // Bevor die Exception aus "new" rausfliegt zerstört die Implementierung jetzt erstmal alle vollständig konstruierten Teilobjekte.
    // (Dann erfolgt noch der Aufruf der zum placement new passenden placement delete Funktion,
    //  die in diesem Fall einfach nix tut. Tut aber hier eigentlich nix zur Sache.)
    // D.h. "a" ist hier wieder nur eine Bytewurst, "A" Objekt gibt es keines
    
    // Jetzt "verlässt" die Exception den "new" Ausdruck.
    
    // --> Unwinding -> Scope-Exit
    
    // Und ich behaupte mal dass beim Scope-Exit wieder "a.A::~A();" aufgerufen wird, weil die Implementierung vermutlich davon ausgehen kann,
    // dass man solche komischen Dinge einfach nicht macht.
    

    Ja, soweit hab ich mir das auch vorgestellt.

    // Und der Aufruf "a.A::~A();" während des Scope-Exit ist UB, weil auf ein Objekt zugegriffen wird, welches gar nicht existiert.
    

    Nochmal zur Klarstellung: Ist das explicit UB?
    Weil hätte man einen Destruktor der mit sowas klar kommmen würde, wäre das ja eig. kein UB...


  • Mod

    Ist das explicit UB?

    Da kein neues Objekt in dem Speicherplatz erstellt wurde (placement-new schlug schließlich fehl), ist nach wie vor keines Vorhanden, daher UB. (§12.4/15)

    Weil hätte man einen Destruktor der mit sowas klar kommmen würde, wäre das ja eig. kein UB...

    Eigentlich, ja. Sogar wenn das doppelte Aufrufen kein Problem für das Programm darstellt. Dem Standard geht es um das Konzept. Ihm ist egal, was der Destruktor macht, es zählt nur dass er aufgerufen wird.



  • Arcoth schrieb:

    Ist das explicit UB?

    Da kein neues Objekt in dem Speicherplatz erstellt wurde (placement-new schlug schließlich fehl), ist nach wie vor keines Vorhanden, daher UB. (§12.4/15)

    Weil hätte man einen Destruktor der mit sowas klar kommmen würde, wäre das ja eig. kein UB...

    Eigentlich, ja. Sogar wenn das doppelte Aufrufen kein Problem für das Programm darstellt. Dem Standard geht es um das Konzept. Ihm ist egal, was der Destruktor macht, es zählt nur dass er aufgerufen wird.

    Ah, ok.



  • Soweit ich weiss darf man nachdem a zerstört wurde (wie im Beispiel oben, also nur Destruktoraufruf, keine Freigabe des Speichers) nichtmal mehr die Adresse von a in einen A* reintun.
    Oder einen A* der vorher schon auf a gezeigt hat in einen anderen A* kopieren.

    Also die gleiche Regel die auch hier UB macht:

    A* a = new A; // OK
    delete a; // OK
    A* b = a; // UB
    

    Ob es erlaubt die die Adresse von a in einen void* oder char* reinzutun weiss ich nicht. WENN man sie schon als void* oder a* hat, dann müsste es OK sein, weil als Bytewurst darf man das "Objekt" (das ja keins mehr ist -- zumindest kein A mehr) in dem Zustand immer noch behandeln.


Anmelden zum Antworten