[gelöst] c++, Destructor wird mit falschem Objekt (oder falschem Attributwert) gerufen (?)


  • Ausgabe:

    Code
    start
    Constructor rvo c = A
    p 1
    Constructor rvo c = B
    Destructor rvo c = B
    end
    Destructor rvo c = B


    OK, der Constructor wird 2 mal gerufen, und der Destructor auch.

    Trotzdem bin ich beunruhigt: das Object mit m_c ==' A ' wird nicht zerstört :( . Was, wenn da jetzt was wichtiges drin stehen würden, z.B. Speicher freigeben, der mit 'A' assoziiert ist?


    ~ Markus

    Client1: ASUS P5QC, Dual Core 3G, Cine S2, Ext. Board von TBE, Xubuntu 20.04, VDR 2.6x

    Client2: RPI3

    Server: RPI4, Sundtek SkyTV Dual 2x

  • Ich denke da musst du einen Assignment-Operator für deine Klasse definieren (vgl. z.B. https://stackoverflow.com/a/31102435:(

    Damit sieht das so aus:

    Code
    $ ./a.out
    start
    Constructor rvo c = A
    p 1
    Constructor rvo c = B
    Assignment operator A=B
    Destructor rvo c = B
    end
    Destructor rvo c = A

    yaVDR-Dokumentation (Ceterum censeo enchiridia esse lectitanda.)

    Einmal editiert, zuletzt von seahawk1986 ()

  • Jeder hat seinen stil, aber ein paar Dinge würde ich anders machen.


    Code
    using namespace std;

    Besser ohne diese using.

    Ist aber hier kein Muss, da nicht in einer header Datei. Geschmacksfrage.


    Wenn man aber oft mit der STL arbeitet, dann lernt man den Vorsatz std:: wirklich zu schätzen.

    Dann sieht man das nicht als 'notwendiges Übel', sondern eher als Teil absichtlich besser verständlichen Codes an.


    Es macht den Code lesbarer und auch eine google Suche nach 'std::string' findet den richtigen Treffer in den ersten zwei Ergebnissen,

    wer aber nur nach 'string' googled, der landet mit hunderten falschen Treffern auf der Nase.

    Ebenso mit den anderen allgemeingültigen Namen in der STL, sowas wie vector oder array.



    Der Aufwand des Tippens des (zuerst lästigen) 'std::' ist vernachlässigbar, bis man denn deswegen ein mal auf die Nase gefallen ist und sehr viel Zeit sinnlos verplempert hat.



    Code
    rvo(char c) { m_c = c; cout << "Constructor rvo c = " << c << endl; }


    besser fände ich hier, m_c vor dem Eintritt in den constructor zu initialisieren und auch private zu halten.

    Code
    rvo(char c)  :  m_c(c) { std::cout << __PRETTY_FUNCTION__ << ": (c = " << c << ')' << std::endl; }




    Code
    int main ()
    {
       cout << "start" << endl;
       rvo t = rvo('A');
       cout << "p 1" << endl;
       t =  rvo('B');
       cout << "end" << endl;
    }


    Dieser code ist in mehrfacher Weise seltsam.

    't' wird nie benutzt, der compiler wird mit -O2 oder -O3 evtl. den ersten Aufruf oder gar den zweiten komplett überspringen.

    Der Programmablauf wird evtl. aus der Sicht eines compilers im Ergebnis gleich sein.


    Ich würde eher schreiben

    Code
    rvo t1('A');
       rvo t2('B');


    Das erspart ein copy assignment, siehe seahawk. Würde der compiler mit optimization wahrscheinlich eh umbauen.

    Wenn du keinen copy assignment operator definierst, wird wohl einer implizit angelegt.


    Der Speicher des rvo('A') deiner Variante müsste von posix her beim Beenden des Programmes immer abgeräumt werden,

    aber ob dann korrekt der Destructor aufgerufen wird, noch bevor das ganze Object aus dem Speicher entfernt ist, können nur Spezis beantworten.

    Würde ich nicht drauf anlegen.



    Mit der zweiten Variante werden t1 und t2 abgeräumt, sobald main() verlassen wird.

    Ob sie ohne Benutzung der Variable wegoptimiert werden, ist ne zweite Sache.


    Gleichwertig wären zwei pointer, solange man nicht die delete vergisst.


  • Danke, seahawk1986 !


    Ich habe jetzt gelernt:


    a) Return value optimization funktioniert nicht, wenn ich

    Code
    t =  rvo('B');

    aufrufe. Es wird ein neues Objekt (rvo('B')) angelegt, der copy constructor wird aufgerufen, und anschließend wird das neu angelegte Objekt (rvo('B')) wieder zerstört.


    b) Wenn ich einen destructor definiere, der etwas wichtiges tut (z.B. Speicher freigeben), muss ich zwingend auch einen =operator anlegen, der das beim Kopieren macht.


    ~Markus

    Client1: ASUS P5QC, Dual Core 3G, Cine S2, Ext. Board von TBE, Xubuntu 20.04, VDR 2.6x

    Client2: RPI3

    Server: RPI4, Sundtek SkyTV Dual 2x

  • Gleichwertig wären zwei pointer, solange man nicht die delete vergisst.

    Da würde sich std::unique_ptr (kein Overhead) bzw. std::shared_ptr (etwas Overhead durch den Reference Counter) anbieten, damit man keinen Raw-Pointern hinterherräumen muss, wenn die den Scope verlassen.

    yaVDR-Dokumentation (Ceterum censeo enchiridia esse lectitanda.)

  • >> a) Return value optimization funktioniert nicht, wenn ich


    Return value optimization bezieht sich auch den Rückgabewert einer Funktion, soweit ich das verstehe. Bevorzugt, wenn ein unbenanntes object/class zurück gegeben wird. Sowas wie


    Code
    rvo foo(char c) { return rvo(c); }


    Hast du ja nicht, hier wäre das Copy elisionwas du meinst.

    Details dazu z.B. hier.


    >> Es wird ein neues Objekt (rvo('B')) angelegt, der copy constructor wird aufgerufen, und anschließend wird das neu angelegte Objekt (rvo('B')) wieder zerstört.

    Kann sein, aber muss nicht. Je nach Compiler und dessen Settings, in C++17 und c++20 auch jeweils konkretisiert und für einigte Fälle festgelegt.


    Ein schönes Beispiel mit drei möglichen Ergebnissen dazu in der ersten Antwort im Link.

  • Hi,


    Ich habe noch https://en.wikipedia.org/wiki/…ree_%28C++_programming%29 gefunden.

    Sehr spanned, wie C++ das unnötige Duplizieren von Objekten (bzw. von Datenbereichen in Objekten) in verschiedenen Situationen vermeidet.


    ~ Markus

    Client1: ASUS P5QC, Dual Core 3G, Cine S2, Ext. Board von TBE, Xubuntu 20.04, VDR 2.6x

    Client2: RPI3

    Server: RPI4, Sundtek SkyTV Dual 2x

    Einmal editiert, zuletzt von MarkusE ()

  • Deshalb favorisiere ich mittlerweile "immutable"/"const" Variablen, denen man keinen neuen Wert zuweisen kann. Es gibt da einfach zu viele Fallstricke.


    Es hilft dann natürlich, wenn man eine Programmiersprache hat, die das Erzeugen eines neuen Objekts aus einem alten mit leichten Änderungen vereinfacht. Stichwort "with"-Keyword wie z.B. in F# oder C#.

    Auch in JavaScript/TypeScript benutze ich immutable Objekte. Wenn ich ein neues mit einer geänderten Property brauche, benutze ich den spread-Operator.


    Code
    let a = { c = 'A' };
    a = { ...a, c = 'B' };

    Aber eigentlich mache ich eher sowas:

    Code
    const a = { c = 'A' };
    const b = { ...a, c = 'B' };
    // Fehler wenn, weil const "Variable" nicht neu zugewiesen werden können:
    a = { c = 'A2' };


    Aber in diesen Sprachen gibt es auch keinen "richtigen" Destruktor, sondern nur sowas wie Finalizer bzw. IDisposable. Da ist das Aufräumen etwas strukturierter.


    C++ ist hart. 😎


    Lars.

Jetzt mitmachen!

Sie haben noch kein Benutzerkonto auf unserer Seite? Registrieren Sie sich kostenlos und nehmen Sie an unserer Community teil!