return by value - deep/shallow copy



  • 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



  • Kann man es eigentlich irgendwie erzwingen, dass bei return anstatt gemoved wird kopiert wird?

    Habe hier derzeit eine Bib, die evtl. schlecht programmiert ist.
    Ich denke das Objekt, dass gemoved wird gibt seinen Speicher frei, obwohl es diese an das neue Objekt abgeben sollte, deshalb wollte ich das moven erstmal umgehen.

    Gruß 🙂



  • Die Verwendung der Begriffe "deep" und "shallow" bzgl Default-Copy-Ctor finde ich irgendwie wenig sinnvoll. Es wird einfach elementweise kopiert. Was das bedeutet, hängt natürlich von den Typen ab. Beispiel:

    struct dings {
      std::vector<int> elem;
    }
    

    Obwohl die sizeof(elem) Bytes hier nur für einen Zeiger und ein paar Ints verwendet werden, referenziert der Zeiger die Vektor-Elemente, die natürlich, wenn ein dings-Objekt kopiert wird, mitkopiert werden ...

    MatheStein schrieb:

    Kann man es eigentlich irgendwie erzwingen, dass bei return anstatt gemoved wird kopiert wird?

    Bestimmt:

    ...
    
    template<class T>
    T const& constify(T& x) {return x;}
    
    string GCC_macht_hier_NRVO() // eine Form der "copy elision"
    {
      string r = "hello";
      return r;
    }
    
    string move_erzwingen()
    {
      string r = "hello";
      return std::move(r); // blöde Idee, da NRVO natürlich besser ist als ein move
    }
    
    string copy_erzwingen()
    {
      string r = "hello";
      return constify(r); // noch blödere Idee, da hier unnötig kopiert wird
    }
    
    ...
    

    Die "copy elisions", die der Standard erlaubt, kann man sonst beim GCC über einen speziellen Schalter deaktivieren. Bin gerade zu faul, nachzugucken, wie der heißt.

    Ich möchte noch darauf hinweisen, dass das const im letzten Fall gar nicht der springende Punkt ist, sondern, dass hier eine Funktion eine Referenz zurückliefert. Hier darf und kann kein RVO angewandt werden. Wenn man r nur als const string deklarieren würde, wäre RVO immer noch machbar, ein move natürlich nicht mehr.



  • Danke für deine Hilfe.

    Ich denke in deinem Beispiel wir das move erzwungen, da die Rückgabe von "std::move(r)" ein r-value ist oder?

    Warum genau erzwingt man mit "constify(r)" das Kopieren? Hier wird zwar ein l-value zurückgegeben, aber wenn ich das richtig verstanden habe werde in return-Anweisungen auch l-values gemoved, wenn besagtes l-value nach dem return seine Gültigkeit verliert oder?

    Gruß 🙂



  • @kk: Der Compiler wird dies natürlich inlinen, dadurch wird dein Resultat irgendwas von der Form

    //oder const_cast? bin mir gerade gar nicht sicher...
    return static_cast<const string&>(r);
    

    sein. Greift das Argument dann immer noch? Der Compiler "sieht" ja, dass da nichts passiert.


Anmelden zum Antworten