return by value - deep/shallow copy



  • Smartpointer bzw. RAII und das Problem der Tiefen/Flachen-Kopien sind orthogonal zueinander.
    Auch wenn Du einen shared_ptr benutzt, hast Du nach wie vor nur flache Kopien.



  • Danke 🙂

    Ich meinte bzgl. der smart Pointer jetzt nur Erstellen/Zerstören der Speicherbereiche und nicht die Modifikation, habe ich vergessen beizuschreiben

    Gruß



  • Ne, nix. Mit C++11 Move-Konstruktoren ist das Problem doch längst gelöst, wundert mich, dass das noch niemand erwähnt hat. oO



  • Danke für deine Antwort 🙂

    Kann man es denn irgendwie bewerkstelligen, dass bei der return-Anweisung gemoved wird anstatt kopiert, d.h. dass der move-Konstruktor verwendet wird anstatt der copy-Konstruktor?



  • Das passiert automatisch, sofern ein Move-Konstruktor vorhanden ist.



  • Kann man dann zusammenfassend festhalten, dass wenn man eine lokale Variable returned automatisch der move-Konstruktor gewählt wird, sofern er vorhanden ist (ansonsten der copy-Konstruktor) und wenn eine globale Variable returned wird stets der copy-Konstruktor?

    Gruß 🙂



  • Ne, es gibt auch noch statische Variablen etc.
    Es wird immer kopiert, es sei denn durch das return statement wird die Lebenszeit zurückgegebenen Objekts beendet, dann wird gemoved. Irgendwie logisch. 😉



  • Ja stimmt 🙂

    Vielen Dank 🙂



  • Nein das stimmt so nicht. Die Ausgabe dieses Programms

    #include <iostream>
    
    class A
    {
    public:
        A()
        {
            std::cout << "default" << std::endl;
        }
        A( const A& )
        {
            std::cout << "copy" << std::endl;
        }
        A( A&& )
        {
            std::cout << "move" << std::endl;
        }
        ~A()
        {
            std::cout << "destruction" << std::endl;
        }
    };
    
    A foo()
    {
        A a;
        return a;
    }
    
    int main()
    {
        A a = foo();
    }
    

    Ist bei mir beispielsweise nur

    default
    destruction
    

    Der compiler konstruiert das A aus foo direkt dorthin, wo es nach der Funktion gebraucht wird, d.h. es wird nie kopiert. Stattdessen übergibt main() foo() implizit einen return_ptr (oder wie man das auch nennen will), wohin das Objekt konstruiert werden soll, d.h. auf den Stackbereich von main(). Dadurch kann foo() einfach returnen und braucht a gar nicht zu kopieren oder zu moven.
    Hier ist sowas nicht möglich, da je nach Pfad ein anderes Objekt returned werden kann (zumindest moved mein gcc hier):

    A foo()
    {
        A a;
        A b;
        if( true )
            return a;
        else
            return b;
    }
    

    Ich meine diese Optimierung heißt 'return value optimization', danach kannst du mal googlen. Es gibt auch einige gute Tutorials zum Thema move, z.B. 'moving forward'.



  • Danke für deine Hilfe, bekommt man diese Ausgabe auch, wenn man alle Optimierungen deaktiviert?



  • Nein, RVO wird üblicherweise nur im Releasemodus durchgeführt. Du bekommst also im Debugmodus eine andere Ausgabe.



  • Bei mir schon, ja. Ich weiß aber nicht, ob diese Optimierung verpflichtend im Standard festgelegt ist, oder ob man auch standardkonform immer kopieren/moven darf.



  • Sie wird wohl nicht verpflichtend sein, wenn im Debugmodus die "normale" Ausgabe kommt. 😉



  • Der Debug-Modus wird beim GCC mit -g eingeleitet oder? Bei mir ändert sich da nichts an der Ausgabe 😞



  • Kann sein, dass GCC das immer an hat.



  • Ich bekomme aber die Ausgabe (mit oder ohne -g), die eigentlich im Release-Modus erscheinen sollte:

    default
    destruction



  • Sag ich doch. GCC macht RVO scheinbar einfach immer, warum auch nicht?



  • Daran soll's nicht scheitern.

    #include <cstdlib>
    #include <iostream>
    
    struct A {
      A()           { std::cout << "default"     << std::endl; }
      A(A const &)  { std::cout << "copy"        << std::endl; }
      A(A &&)       { std::cout << "move"        << std::endl; }
      ~A()          { std::cout << "destruction" << std::endl; }
    };
    
    A foo()
    {
      A a, b;
    
      // RVO verhindern.
      return std::rand() % 2 == 0 ? a : b;
    }
    
    int main()
    {
      A a = foo();
    }
    

    gibt bei mir

    default
    default
    copy
    destruction
    destruction
    destruction
    

    Move gibt's mit

    return std::move(std::rand() % 2 == 0 ? a : b);
    

    a in main wird allerdings von gcc nach wie vor direkt initialisiert. Grundlage ist g++ 4.7.0 mit -std=c++11.



  • MatheStein schrieb:

    Der Debug-Modus wird beim GCC mit -g eingeleitet oder? Bei mir ändert sich da nichts an der Ausgabe 😞

    -g erzeugt nur Debug-Symbole.
    -O0 setzt die Optimierungen auf 0, also Debug-Build. -O1, -O2, -O2.5, -Os und -O3 erzeugen Realese-Builds.



  • Bei mir bleibt die Ausgabe stets gleich, auch mit -O0


Anmelden zum Antworten