Smart Pointer statt 'new' und 'delete'?


  • 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.


  • Mod

    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.

    Doch.

    [..] after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any glvalue that refers to the original object may be used but only in limited ways. [..]. Otherwise, such a glvalue refers to allocated storage (3.7.4.2), and using the properties of the glvalue that do not depend on its value is well-defined. [...]

    Also die gleiche Regel die auch hier UB macht:

    Da ist aber der entscheidende Unterschied, dass der Speicher auch nicht mehr alloziert, sondern schon freigegeben ist.



  • Ah, cool!
    Wieder was dazugelernt.


Anmelden zum Antworten