return by value - deep/shallow copy



  • Ja man kann dies mit Smart Pointern loesen. Smart Pointer sind eine Moeglichkeit RAII umzusetzen.

    Es gibt allerdings ganz verschiedene Smart Pointer. Es funktionieren nicht alle ueber Reference Count. Ich denke fuer diesen Fall waere ein shared_ptr angebracht.

    Hier werden ein paar Smart Pointer aus der boost library erklaert. shared_ptr ist im namespace tr1 und wird von den modernen Compilern unterstuezt.



  • Vielen Dank für deine Hilfen 🙂



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


Anmelden zum Antworten