C++0x: Forcieren des Move-Konstruktors



  • Hallo zusammen,

    in manchen Fällen, weiß man ja, dass ein Objekt nach einem bestimmten
    Zeitpunkt nicht mehr benötigt wird. Die letzte Kopie kann folglich mit Hilfe
    des Move-Konstruktors gemacht werden.
    Diese Information möchte ich dem Compiler explizit mitgeben.
    Deswegen habe ich mir überlegt einen expliziten cast zu machen:

    #include<vector>
    #include<iostream>
    
    class A {
    public:
            A(){}
            A(const A&){
                    std::cout << "copy" << std::endl;
            }
            A(A&& a){
                    std::cout << "move" << std::endl;
            }
    };
    
    int main()
    {
            A a1;
            A a2;
            std::vector<A> vec;
            vec.reserve(2);
            vec.push_back(a1); // Hier wird der Copy-Kontruktor aufgerufen
            vec.push_back(static_cast<A&&>(a2)); // Hier wird der Move-Kontruktor aufgerufen
            return 0;
    }
    

    Es scheint auch einwandfrei zu funktionieren:

    copy
    move

    Allerdings weiß ich nicht, ob das so (nach Standard C++0x) erlaubt ist.
    Kann ich sowas verwenden?
    Muss ich eventuell irgendwas spezielles beachten? (abgesehen davon, dass das Ursprungsobjekt 'kaputt' ist)

    Gruß,
    XSpille



  • XSpille schrieb:

    Allerdings weiß ich nicht, ob das so (nach Standard C++0x) erlaubt ist.
    Kann ich sowas verwenden?

    Ja, das ist korrekt so. Allerdings ist dafür das Funktionstemplate std::move aus dem utility-Header gedacht:

    vectory<string> vec;
    string foo = ...;
    foo += ...;
    vec.push_back(std::move(foo));
    

    std::move wird aber genau denselben Cast durchführen.

    XSpille schrieb:

    Muss ich eventuell irgendwas spezielles beachten?

    Nö. Je nachdem, was der Move-Konstruktor anstellt, wird sich das Ursprungsobjekt in einem anderen Zustand befinden. Es ist wenigstens fehlerfrei zerstörbar (also immer noch "gültig" zu einem gewissen Grad). Von den meisten Klassen, die einen Zuweisungsoperator bieten, würde man auch erwarten, dass sich ein neuer Wert einem solchen Objekt immer noch zuweisen lässt. Alles weitere ist Sache des Klassendesigners. Er kann natürlich weitere Garantien darüber machen, was innerhalb eines Move-Konstruktors eigentlich passiert und welche Operationen danach noch durchgeführt werden können. Beispielsweise jst klar, dass ein moved-from unique_ptr-Objekt ein Nullzeiger speichern wird.



  • Danke für die ausführliche Antwort krümelkacker 👍



  • Wie kann ich denn das move für eine map forcieren?

    int main()
    {
            A a1;
            A a2;
            std::map<int, A> map;
            map[0] = std::move(a1);
            std::cout << "---" << std::endl;
            map.insert(std::pair<int, A>(1, std::move(a2)));
            //map.insert(std::move(std::pair<int, A>(1, std::move(a2)))); // bringt auch nichts...
            return 0;
    }
    

    move
    copy
    ---
    move
    move
    copy

    Ist da beim gcc 4.5.2 etwas noch nicht fertig implementiert, geht es
    gar nicht oder welche Änderung muss ich machen?

    Gruß,
    XSpille

    EDIT:
    Natürlich kann ich als value nen Zeiger benutzen:
    Dieser müsste jedoch (ohne wrappen in Smart-Pointer) manuell zerstört werden...

    std::map<int, A*> map;
            map[0] = new A(std::move(a1));
            std::cout << "---" << std::endl;
            map.insert(std::pair<int, A*>(1, new A(std::move(a2))));
    

    move
    ---
    move

    Aber geht es auch anders?



  • Probier mal mit pair<int const, A> bzw. einfach map<>::value_type.



  • hustbaer schrieb:

    Probier mal mit pair<int const, A> bzw. einfach map<>::value_type.

    Geht leider auch nicht 😞
    Danke trotzdem für diese Idee!



  • mag sein, dass std::pair von libstdc++ noch nicht auf dem aktuellen Stand ist.



  • vc10 kompilierts zu

    move
    move
    


  • Okay...

    gcc 4.6.0 kompiliert

    std::map<int, A> map;
            map[0] = std::move(a1);
            std::cout << "---" << std::endl;
            map.insert(std::map<int, A>::value_type(1, std::move(a2)));
    

    zu

    move
    move
    move
    ---
    move
    move



  • move
    move
    move
    ---
    move
    move

    Gibst Du eigentlich auch "move" bei einer Move-Zuweisung aus? Ich kann mir so oder so nicht ganz erklären, warum beim ersten Teil 3mal move auftaucht.

    Übrigens soll es möglich sein, eine map auch so zu befüllen:

    map.insert({1,std::move(a1)});
    map.emplace(2,std::move(a2));
    


  • krümelkacker schrieb:

    move
    move
    move
    ---
    move
    move

    Gibst Du eigentlich auch "move" bei einer Move-Zuweisung aus? Ich kann mir so oder so nicht ganz erklären, warum beim ersten Teil 3mal move auftaucht.
    [/cpp]

    Nein... Ich habe genau den geposteten Code für Klasse A verwendet...

    krümelkacker schrieb:

    Übrigens soll es möglich sein, eine map auch so zu befüllen:

    map.insert({1,std::move(a1)});
    map.emplace(2,std::move(a2));
    

    Danke für diese Anmerkung! 👍
    emplace kannte ich bisher nur von vector & die andere Schreibweise
    hab ich höchstens mal hier im Forum gesehen.



  • #include<vector> 
    #include<iostream> 
    
    class A { 
    public: 
            A(){} 
            A(const A&){ 
                    std::cout << "copy" << std::endl; 
            } 
            A(A&& a){ 
                    std::cout << "move" << std::endl; 
            } 
    
    				~A() {
    					std::cout << "~A" << std::endl;
    				}
    }; 
    
    int main() 
    { 
            A a1;
            std::vector<A> vec;
            vec.push_back(std::move(a1)); // Hier wird der Move-Kontruktor aufgerufen 
            return 0; 
    }
    

    Was ich in diesem Fall nicht verstehe ist, warum der Destruktor 4 mal aufgerufen wird. Ich dachte, mit dem Move-Konstruktor wird das gleiche Objekt in den vector gepackt, daher sollte doch auch nur einmal der Destruktor für a1 ufgerufen werden?

    $ ./test
    move
    ~A
    ~A



  • Ich meine natürlich 2 mal - 4 mal im ersten Beispiel mit a1 und a2...



  • C++0X schrieb:

    Was ich in diesem Fall nicht verstehe ist, warum der Destruktor 2 mal aufgerufen wird. Ich dachte, mit dem Move-Konstruktor wird das gleiche Objekt in den vector gepackt, daher sollte doch auch nur einmal der Destruktor für a1 ufgerufen werden?

    Es wird nicht das gleiche Objekt in den vector gepackt.
    Es wird ein neues Objekt erstellt, jedoch mit der Erlaubnis, die Inhalte des
    ursprünglichen Objekts zu zerstören. (Der Destruktor muss noch funktionieren)

    So kann man sich beispielweise eine tiefe Kopie ersparen: (okay hier nur ein int* ^^)

    class A { 
    public: 
            A(A&& a){ 
                    this->i = a.i;
                    a.i = 0; // ansonsten funktioniert delete i; im Destruktor nicht mehr (doppel-delete)
            }
    private:
            int* i;
    }
    

    So übernimmt man einfach Teile des Original-Objektes, da dieses sie nicht mehr braucht.

    Gruß,
    XSpille



  • C++0X schrieb:

    Was ich in diesem Fall nicht verstehe ist, warum der Destruktor [2] mal aufgerufen wird. Ich dachte, mit dem Move-Konstruktor wird das gleiche Objekt in den vector gepackt, daher sollte doch auch nur einmal der Destruktor für a1 ufgerufen werden?

    Warum das denn? Du hast doch zwei A-Objekte, a1 und vec[0]. Es wird nur statt dem Kopierkonstruktor der Movekonstruktor aufgerufen, um vec[0] zu erzeugen. Was im Movekonstruktor passiert ist ganz allein Deine Sache. Darüber hinaus gibt es keine Magie.

    Vielleicht hast Du duch aber auch von dem Namen der Hilfsfunktion "move" irritieren lassen. Innerhalb von move passiert nichts besonderes. Es ist hier nur eine Abkürzung für static_cast<A&&>(a1). Dieser Ausdruck bezieht sich immer noch auf a1, ist aber jetzt kein Lvalue-Ausdruck mehr sondern ein Rvalue-Ausdruck.



  • [quote="XSpille"]

    C++0X schrieb:

    So übernimmt man einfach Teile des Original-Objektes, da dieses sie nicht mehr braucht.

    Ok, jetzt habe ich's verstanden. Dachte zuerst, der Move-Ausdruck würde bewirken, dass das eigentliche Objekt "verschoben" wird.

    Vielen Dank für die Erklärung.



  • doppelpost



  • In C++ können Objekte nicht in dem Sinn "verschoben" werden. Man kann nur ein neues Objekt erstellen, das dem alten Objekt seine Resourcen "klaut". Bzw. eine Zuweisung machen die einem anderen Objekt seine Resourcen "klaut". Das ist das was man im Zusammenhang mit C++ als "Move Semantik" bezeichnet.

    Das "andere" Objekt, dem die Resourcen geklaut werden, wird dabei in einem beliebigen aber "gültigen" Zustand hinterlassen, in dem es zumindest noch zerstört werden kann. Und es muss auch zerstört werden, da es ja immer noch existiert.


Anmelden zum Antworten