Dtor-Ausfuehrungszeitpunkt: vor oder nach 'return' Anweisung?



  • Hi!

    Wird der Dtor einer lokalen Variable VOR oder NACH der return-Anweisung ausgefuehrt?

    Speziell gehts um diese 2 Codezeilen:

    lock_guard<mutex> lock(mtx);
    	return mymap[chrnum];
    

    der Lock soll den Aufruf des operator[] schuetzen, was er natuerlich nicht macht, wenn der lock_guard-Dtor zu frueh aufgerufen wird.


  • Administrator

    Blue-Tiger schrieb:

    Wird der Dtor einer lokalen Variable VOR oder NACH der return-Anweisung ausgefuehrt?

    Logischerweise NACH. Wäre schlimm wenn nicht, da man dann keine lokalen Variablen zurückgeben könnte:

    std::string getString()
    {
      std::string name = "hello";
      return name;
    }
    

    Wenn er vorher ausgeführt werden würde, dann wäre dies undefiniertes Verhalten 😉

    Grüssli



  • Hand->Stirn *patsch*!

    ja, das ist klar 🙂 Thx!



  • Blue-Tiger schrieb:

    Wird der Dtor einer lokalen Variable VOR oder NACH der return-Anweisung ausgefuehrt?

    Wann wird der Scope verlassen?



  • Blue-Tiger schrieb:

    Wird der Dtor einer lokalen Variable VOR oder NACH der return-Anweisung ausgefuehrt?

    Speziell gehts um diese 2 Codezeilen:

    lock_guard<mutex> lock(mtx);
       return mymap[chrnum];
    

    der Lock soll den Aufruf des operator[] schuetzen, was er natuerlich nicht macht, wenn der lock_guard-Dtor zu frueh aufgerufen wird.

    Da brauchst Du Dir keine Sorgen machen. Aber man kann nicht direkt sagen, dass die lokalen Objekte VOR oder NACH der return-Anweisung zerstört werden. Eher so mittendrin. Und manchmal auch gar nicht. Bei einem return passiert im Prinzip folgendes:
    1 Der Rückgabewert (falls vorhanden) wird berechnet/konstruiert
    2 Lokale, automatische Objekte werden zerstört
    3 Die Ausführung geht beim Aufrufer weiter

    Der Compiler darf sich aber unnötige Kopien bei (1) sparen. Warum auch Kopie konstruieren und Original gleich wieder in (2) zerstören? Die Optimierung, die das umgeht, nennt sich RVO (return value optimization) oder NRVO (named ...).

    Im Beispiel von Dravere...

    std::string getString()
    {
      std::string name = "hello";
      return name;
    }
    
    void g()
    {
      std::string foo = getString();
    }
    

    kann ein Compiler sogar die drei Objekte name , den return -Wert von getString() und foo zu einem einzigen Objekt zusammenfassen. Dies würde dann in getString konstruiert und bleibt unter dem Namen "foo" weiter am Leben. Das nennt sich "copy elision" und wird von vielen modernen Compilern unterstützt, um die Zahl unnötiger Kopien zu reduzieren. In welchen Situationen das gemacht werden darf, kann man in Abschnitt §12.8 des C++ Standards nachlesen.


  • Administrator

    @krümelkacker,
    Das ist zwar alles schön und gut, aber diese Optimierungen dürfen nichts an der Reihenfolge der aufgerufenen Funktionen ändern. Somit wird zum Beispiel der Destruktor von lock_guard<mutex> ganz sicher nach dem return ausgeführt, daran darf keine Optimierung etwas rütteln.

    Grüssli



  • Dravere schrieb:

    @krümelkacker,
    Das ist zwar alles schön und gut, aber diese Optimierungen dürfen nichts an der Reihenfolge der aufgerufenen Funktionen ändern.

    Das ist IMHO nicht so gut ausgedrückt. Diese Optimierungen führen dazu, dass die Zahl der aufgerufenen Kopierkonstruktoren und Destruktoren reduziert wird (Kopierkonstruktoren vermieden, Destruktoren verzögert und reduziert). Man kann schwer von Reihenfolge sprechen, wenn die Optimierung zwei oder mehr Objekte zu einem einzigen zusammenfasst:

    string foo() {
      string r = "hello";
      return r;
    }
    
    void bar() {
      string x = foo();
      string z = x;
    }
    
    Mit kompletter copy elision:      Ohne copy elision:
    ----------------------------------------------------
    string(char const*) (x und r)     string(char const*)   (Objekt r)
                                      string(string const&) (Objekt t)
                                      ~string()             (Objekt r)
    
                       ---- Rücksprung aus foo ----
    
                                      string(string const&) (x<-t)
                                      ~string()             (Objekt t)
    
                   ---- Zeile 7 komplett ausgewertet ----
    
    string(string const&) (z<-x)      string(string const&) (z<-x)
    ~string()             (z)         ~string()             (z)
    ~string()             (x)         ~string()             (x)
    

    wobei t das temporäre Objekt ist, was foo() liefert. Ich sage auch nicht, dass das in dem Fall des Fragestellers zum Problem führen kann. Es ist nur ein Beispiel dafür, dass nicht alle automatischen, lokalen Objekte vor dem Rückspring zerstört werden. Das ist genau dann nicht der Fall, wenn RVO stattfindet hat.

    Dravere schrieb:

    Somit wird zum Beispiel der Destruktor von lock_guard<mutex> ganz sicher nach dem return ausgeführt, daran darf keine Optimierung etwas rütteln.

    Der Destruktor wird ganz sicher nicht zu früh aufgerufen. Aber ob das "nach dem return" passiert, kommt drauf an, was du unter "return" verstehst. Wenn Du unter "return" nur das Berechnen/Konstruieren des Rückgabewertes verstehst: Ja. Wenn du unter "return" auch den Rückspring meinst: Nein. Das Aufräumen garantiert passiert NACH dem Berechnen/Konstruieren des Rückgabewertes und üblicherweise VOR dem Rücksprung, wenn nicht gerade "copy elision" statt gefunden hat.


  • Administrator

    krümelkacker schrieb:

    Wenn Du unter "return" nur das Berechnen/Konstruieren des Rückgabewertes verstehst: Ja. Wenn du unter "return" auch den Rückspring meinst: Nein.

    Ich meinte das Erstere.

    krümelkacker schrieb:

    Das Aufräumen garantiert passiert NACH dem Berechnen/Konstruieren des Rückgabewertes und üblicherweise VOR dem Rücksprung, wenn nicht gerade "copy elision" statt gefunden hat.

    Nein, der Aufruf findet IMMER vor dem Rücksprung statt, auch wenn copy elision stattfindet. Mach mal in einem Destruktor eine Ausgabe hin und schau dir den ASM Code an, die Ausgabe wird immer davor gemacht. Copy elision kann natürlich trotzdem auftreten. Die Reihenfolge ist genaustens definiert, es wird in der umgekehrten Reihenfolge abgeräumt, dass heisst Destruktoren aufgerufen, wie die Objekte erstellt wurden. An dieser Reihenfolge von Funktionalitäten gibt es nichts zu rütteln. Wie und ob Speicher kopiert wird, ist eine ganz andere Sache.

    Grüssli



  • Ich glaube, wir reden nur aneinander vorbei.


Log in to reply