boost::ptr_vector mit unkopierbaren Objekten



  • Hi,

    ich habe eine Klasse, die nicht kopierbar ist. Allokierte Instanzen will ich in einen boost::ptr_vector stecken, wobei ich aber den Fehler bekomme, dass die Objekte darin nicht geklont werden können:

    #include <boost/ptr_container/ptr_vector.hpp>
    
    class foo {
    	public:
    		foo() {}
    	private:
    		foo( const foo& );
    		foo& operator=( const foo& );
    };
    
    boost::ptr_vector<foo> bar() {
    	boost::ptr_vector<foo> Arr;
    	Arr.push_back( new foo() );
    	return Arr;
    }
    
    int main() {
    	boost::ptr_vector<foo> X = bar();
    }
    

    Mir ist mittlerweile immerhin klar, warum die Objekte geklont werden sollen. Als Abhilfe könnte ich der Funktion bar auch eine Referenz auf den ptr_vector mitgeben und ihn da füllen lassen - ich bin aber grad etwas bockig und sehe nicht ein irgendwas umzubauen nur damit boost zufrieden ist.

    Hat jemand einen anderen Workaround oder eine Idee, einen Alternativcontainer oder Sonstiges? An sich müssen ja nur die Zeiger umkopiert werden, das kann ja nicht so schwer sein; und der Container sollte halt Ownership der Objekte haben.



  • Ok, mir ist grad std::vector<std::auto_ptr<foo> > eingefallen, oder verträgt sich das nicht? Zumindest beim Zurückgeben scheint's zu klappen, kopieren kann man den Container dann natürlich nicht, aber einen std::vector<boost::shared_ptr<foo> > will ich echt nicht einsetzen.



  • asdasdasd schrieb:

    kopieren kann man den Container dann natürlich nicht

    Ich meine natürlich: Man kann dann keine zwei Container-Instanzen mit den gleichen Zeigern gleichzeitig halten.



  • Container und auto_ptr vertragen sich nicht. Nimm shared_ptr aus TR1 oder boost.



  • Du könntest (solange das von boost nicht implementiert wird) den ptr_vector in einer Klasse wrappen, die einen move ctor hat. In dem kannst du ptr_vector::swap verwenden.
    Alternativ kannst du, wenn du keinen vector<shared_ptr<T> > verwenden möchtest, einen vector<unique_ptr<T> > benutzen. Da solltest du aber vorher prüfen ob das in dein System passt, denn mit dem unique_ptr kannst du nicht alles machen wie mit einem shared_ptr.
    Beide Lösungen verlangen nach einem neuen Compiler, der das auch kann.

    Bei der Variante mit Übergabe des Containers als Referenz würde ich ansonsten nach Möglichkeit eher etwas tun wie

    template<typename OutIter>
    void bar(OutIter out)
    {
        for(int i = 0; i < 5; ++i, ++out)
            *out = new foo();
    }
    
    int main()
    {
        boost::ptr_vector<foo> v;
        bar(std::back_inserter(v));
        return 0;
    }
    


  • oki doki, danke



  • Zwei weitere C++98-Möglichkeiten, die mir spontan einfallen:

    void bar(boost::ptr_vector<foo>& out);
    // Vorteil: Keine Anforderungen an Speicherklasse von out
    // Nachteil: Kein Rückgabewert, daher nicht in temporären Ausdrücken einsetzbar
    
    std::auto_ptr< boost::ptr_vector<foo> > bar();
    // Vorteil: Move-Semantik mit C++98
    // Nachteil: Container muss dynamisch angefordert werden
    

    Wenn du C++0x verwendest, brauchst du gar keine Wrapperklasse, sondern kannst direkt den Container verschieben:

    boost::ptr_vector<foo>&& bar();
    


  • Nexus schrieb:

    Wenn du C++0x verwendest, brauchst du gar keine Wrapperklasse, sondern kannst direkt den Container verschieben:

    boost::ptr_vector<foo>&& bar();
    

    Ääh, zeig mal bitte. Muss ich nochmal nachsitzen... 🙄



  • brotbernd schrieb:

    Ääh, zeig mal bitte. Muss ich nochmal nachsitzen... 🙄

    Ich hätte mir das so vorgestellt, muss aber anmerken, dass ich mich noch nicht sehr gut mit RValue-Referenzen auskenne. Um den Code von asdasdasd zu nehmen:

    boost::ptr_vector<foo>&& bar()
    {
        boost::ptr_vector<foo> Arr;
        Arr.push_back( new foo() );
        return std::move(Arr);
    }
    


  • Ich bin da auch noch nicht so ganz sicher mit dem Kram, aber in meinem bisherigen Verständnis dachte ich, das Ziel bräuchte einen Ctor ptr_vector(ptr_vector&&) , der die rvalue Referenz annimmt und ihr den Inhalt klaut.
    Dein Code ruft bei mir zumindest wieder den copy ctor auf und verlangt nach einem clone, wohingegen das hier funktioniert:

    struct Wrap
    {
        Wrap(){}
        Wrap(Wrap&& r)
        {
            v.swap(r.v);
        }
        boost::ptr_vector<foo> v;
    private:
        Wrap(Wrap&);
        void operator=(Wrap&);
    };
    
    Wrap baz()
    {
        Wrap w;
        w.v.push_back( new foo() );
        return w;
    }
    


  • brotbernd schrieb:

    Ich bin da auch noch nicht so ganz sicher mit dem Kram, aber in meinem bisherigen Verständnis dachte ich, das Ziel bräuchte einen Ctor ptr_vector(ptr_vector&&) , der die rvalue Referenz annimmt und ihr den Inhalt klaut.
    Dein Code ruft bei mir zumindest wieder den copy ctor auf und verlangt nach einem clone, wohingegen das hier funktioniert:

    Ich vermute mal Nexus geht davon aus, dass der Ctor ptr_vector(ptr_vector&&)
    bereits vorhanden ist.

    Das move macht er wahrscheinlich aus folgendem Grund:

    For safety reasons a named variable will never be considered to be an rvalue even if it's declared as such; in order to get an rvalue the function template std::move<T>() should be used.

    http://en.wikipedia.org/wiki/C%2B%2B0x#Rvalue_reference_and_move_semantics

    Gruß,
    XSpille



  • XSpille schrieb:

    Ich vermute mal Nexus geht davon aus, dass der Ctor ptr_vector(ptr_vector&&)
    bereits vorhanden ist.

    Ich schrieb ja im ersten Post, ein Wrapper sei nötig solange der Container keinen eigenen move ctor hat. Darum wunderte ich mich, als er sagte es würde auch ohne gehen.

    XSpille schrieb:

    Das move macht er wahrscheinlich aus folgendem Grund: ...

    Ja hm, ich weiß nicht wie das genau geregelt ist, aber beim Rückgabewert hat das bisher bei mir so funktioniert. Ich hab mich da orientiert an:

    1. If A has an accessible copy or move constructor, the compiler may choose to elide the copy
    2. otherwise, if A has a move constructor, v is moved
    3. otherwise, if A has a copy constructor, v is copied
    4. otherwise, a compile time error is emitted.

    aus: http://cpp-next.com/archive/2009/09/move-it-with-rvalue-references/



  • Nexus schrieb:

    Ich hätte mir das so vorgestellt, muss aber anmerken, dass ich mich noch nicht sehr gut mit RValue-Referenzen auskenne. Um den Code von asdasdasd zu nehmen:

    boost::ptr_vector<foo>&& bar()
    {
        boost::ptr_vector<foo> Arr;
        Arr.push_back( new foo() );
        return std::move(Arr);
    }
    

    Ein Beispiel, wie man es nicht machen sollte. Du gibst hier eine Referenz auf ein lokal erzeugtes Objekt zurück. Verwendung dieser Referenz ruft undefiniertes Verhalten hervor. Setze && nur ein, wenn Du genau weißt, was Du tust. Typischer Einsatzzweck von && bzgl Move-Semantik ist der Move-Konstruktor einer Klasse sowie ein Move-Assignment-Operator.

    Das, was Du machen wolltest, muss so aussehen:

    boost::ptr_vector<foo> bar()
    {
        boost::ptr_vector<foo> Arr;
        Arr.push_back( new foo() );
        return Arr;
    }
    

    Wie man sieht, muss man hier gar nichts besonderes machen, um Move-Semantik nutzen zu können. Es sieht nach reinem C++98 Code aus. Und das ist auch der Witz an dem neuen Sprach-Feature. Es werden automatisch einige unnötige Kopien durch Moves ersetzt. Lediglich boost::ptr_vector<foo> muss einen Move-Konstruktor haben. Das war's schon. Auch die Verwendung von std::move ist hier überflüssig und sogar eher schädlich, da dadurch NRVO ausgehebelt werden könnte.

    Im Prinzip wurden die "copy elision"-Regeln nur erweitert. Der C++ Standard erlaubt ja in bestimmten Situationen "copy elisions", also das Wegoptimieren von Kopien. Die C++0x Erweiterung sieht nun so aus, dass ein Compiler, der eine Kopie wegoptimieren dürfte, es aber aus irgendwelchen Gründen nicht kann, soll das Quellobjekt als Rvalue behandeln bei der Überladungsauflösung beim Kontruieren einer "Kopie". Das trifft hier auch auf "Arr" zu. Der C++ Standard sagt, dass, weil "Arr" ein lokales Objekt ist, bei "return Arr;" die sogenannte NRVO durchgeführt werden darf. Ein guter Compiler wie zB der aktuelle GCC kann hier auch NRVO durchführen. Ein C++0x Compiler, der das nicht kann, muss zumindest "Arr" als Rvalue für das Konstruieren des Return-Wertes behandelt. Wenn also boost::ptr_container<foo> einen Move-Konstruktor hat, wird garantiert kein Copy-Konstruktor ausgeführt. Entweder, weil der Compiler NRVO durchführt oder einen Move-Konstruktor nehmen würde.

    kk



  • krümelkacker schrieb:

    Typischer Einsatzzweck von && bzgl Move-Semantik ist der Move-Konstruktor einer Klasse sowie ein Move-Assignment-Operator.

    Wann braucht man das && denn noch?
    Braucht man das && auch außerhalb der Parameter von Funktionsdeklarationen.
    Im Rückgabetyp ja scheinbar nicht, oder?

    Sehe ich es richtig, dass ein Parameter mit dem && immer vorgezogen wird,
    wenn das andere Objekt danach gelöscht wird?

    Wird in so einem Fall

    while(...){
      MyObject a;
      // MyObjectB b;
      ...
      doSomething(a);
    }
    

    eine Funktion mit dieser Signatur

    void doSomething(MyObject&& o);
    

    einer Funktion mit dieser Signatur

    void doSomething(MyObject& o);
    

    vorgezogen wird?

    Was passiert wenn die Zeile mit MyObjectB b; eingefügt wird?
    Wäre es dann immernoch so? Immerhin muss ja (eigentlich) der Destruktor
    von b vor dem von a ausgeführt werden.

    Gruß,
    XSpille



  • krümelkacker schrieb:

    Ein Beispiel, wie man es nicht machen sollte. Du gibst hier eine Referenz auf ein lokal erzeugtes Objekt zurück. Verwendung dieser Referenz ruft undefiniertes Verhalten hervor.

    Ah, vielen Dank für die Korrektur. Das erklärt auch, wieso bei einem kurzen Test kein Move-Konstruktor aufgerufen wurde... Ich dachte zuerst, das läge an einer nicht ganz aktuellen Compiler-Implementierung von C++0x.

    krümelkacker schrieb:

    Setze && nur ein, wenn Du genau weißt, was Du tust. Typischer Einsatzzweck von && bzgl Move-Semantik ist der Move-Konstruktor einer Klasse sowie ein Move-Assignment-Operator.

    Gibt es denn Fälle, in denen man eine RValue-Referenz als Rückgabetyp benutzt, abgesehen von weitergereichten Parametern?

    krümelkacker schrieb:

    Wenn also boost::ptr_container<foo> einen Move-Konstruktor hat, wird garantiert kein Copy-Konstruktor ausgeführt. Entweder, weil der Compiler NRVO durchführt oder einen Move-Konstruktor nehmen würde.

    Danke für die ausführliche Erläuterung!



  • XSpille schrieb:

    Wann braucht man das && denn noch?
    Braucht man das && auch außerhalb der Parameter von Funktionsdeklarationen.
    Im Rückgabetyp ja scheinbar nicht, oder?

    Normalerweise nicht. Ausnahmen sind std::move und std::forward. Es gibt vielleicht noch ein paar andere. Aber als Daumenregel würde ich Dir da recht geben -- also... dass man lieber keine Funktionen schreibt, die Rvalue-Referenzen zurückgeben.

    XSpille schrieb:

    Sehe ich es richtig, dass ein Parameter mit dem && immer vorgezogen wird,
    wenn das andere Objekt danach gelöscht wird?

    Dir ist scheinbar das Lvalue/Rvalue-Konzept noch nicht ganz bekannt. Jeder Ausdruck gehört zu einer von drei sogenannten Werte-Kategorien (engl: "value category"). Es ist eine Eigenschaft eines Ausdrucks und wird zB beim Initialisieren von Referenzen und der Überladungsauflösung berücksichtigt. Wenn soweit klar ist, was es für Werte-Kategorien gibt und wann diese auftauchen, kannst Du dir die Tabelle "Binding and Overloading" hier angucken.

    (Sinngemäßes Zitat)

    XSpille schrieb:

    Wird in so einem Fall

    void doSomething(MyObject& o);  // #1
    void doSomething(MyObject&& o); // #2
    
    while(...){
      MyObject a;
      //MyObject b;
      ...
      doSomething(a);
    }
    

    #1 oder #2 aufgerufen?

    #1, da a kein Rvalue-Ausdruck sondern ein Lvalue-Ausdruck ist.

    XSpille schrieb:

    Was passiert wenn die Zeile mit MyObjectB b; eingefügt wird?
    Wäre es dann immernoch so? Immerhin muss ja (eigentlich) der Destruktor
    von b vor dem von a ausgeführt werden.

    Ja und? a ist immer noch ein Lvalue-Ausdruck und es wird immernoch #1 aufgerufen. Und der Destruktor von b wird dann immer noch vor dem Destruktor von a ausgeführt.

    Du kannst aber explizit mit std::move sagen, dass Dir nichts mehr an a liegt:

    doSomething(std::move(a));
    

    Und std::move sieht in etwa so aus:

    template<class T> T&& move(T& x) {return static_cast<T&&>(x);}
    

    Damit liefert move einen Rvalue-Ausdruck (genauer: Xvalue) und doSomething #2 wird aufgerufen. Tatsächlich "bewegt" sich hier gar nichts. std::move gibt einem nur eine Referenz zurück. Dies ist aber eine unbenannte Rvalue-Referenz und gehört daher selbst der Rvalue-Kategorie an. Beachte, dass, nachdem man eine benannte Rvalue-Referenz initialisiert hat, die Verwendung des Namens als Ausdruck aber selbst ein Lvalue-Ausdruck ist.

    Rvalue-Referenzen funktionieren fast genauso wie Lvalue-Referenzen, nur dass die "Binding and Overloading"-Regeln etwas anders aussehen, siehe Tabelle aus der Artikelserie. Darüber hinaus gibt es noch zwei Besonderheiten. 1. Es gibt ein sogenanntes "Reference-Collapsing":

    T     | T&&   | T&        Quasi  & + &  = &
    ------+-------+------            & + && = &
    int   | int&& | int&            && + &  = &
    ------+       |                 && + && = &&
    int&& : [b]int&&[/b] | [b]int&[/b]
    --------------+
    int&  : [b]int&[/b]  : [b]int&[/b]
    

    2. Es gibt eine Sonderregel bei der Template-Argument-Deduktion. Taucht irgendwo "P&&" als Funktions-Parameter auf, wobei P ein Template-Typ-Parameter des Funktionstemplates ist, wird P zu einer Lvalue-Referenz deduziert, falls das Funktionsargument ein Lvalue war. Also:

    // Je nachdem, was T ist, kann T&& auch eine Lvalue-Reverenz sein
    template<class T> void sink(T&&);
    
    struct blah {};
    
    blah         source1(); // liefert [b]R[/b]value, genauer: [b]PR[/b]value (pure rvalue)
    blah      && source2(); // liefert [b]R[/b]value, genauer: [b]X[/b]value (eXpiring/eXpendable)
    blah      &  source3(); // liefert [b]L[/b]value
    
    void test() {
      //                  T        T &&
      // ---------------------------------
      sink(source1()); // blah     blah &&
      sink(source2()); // blah     blah &&
      sink(source3()); // blah &   blah & (reference collapsing)
    }
    

    Was in einer Deiner Funktionen (oder Konstruktoren) passiert, die/der eine Rvalue-Referenz entgegen nimmt, ist ganz allein Deine Sache. Du darfst das Objekt aber verändern, ohne dass es jemanden stören würde (auch den Aufrufer nicht), da es entweder ein "temporäres Objekt" ist oder weil der Aufrufer explizit durch ein std::move oder std::forward<U> (wobei U keine Lvalue-Referenz ist) gesagt hat, dass es irgendwie verändert werden darf. Es wird jedenfalls nichts einfach so "zerstört", nur weil Du Rvalue-Referenz-Parameter benutzt.



  • Vielen Dank, krümelkacker, für die ausführlichen Erklärungen.

    Was mich jetzt noch zu dem "reference collapsing" interessiert:

    template<class T>
    void f(T&& t)   //<-- aha, ich nehme einen RValue, also ein temporäres Objekt
    {
        T t2(std::move(t));   //<-- also darf es "zerstört" werden
    }
    

    Nur leider darf es ja eben nicht "zerstört" werden, wenn eigentlich ein LValue übergeben wurde.
    Ich darf also trotz RValue-Referenz nicht mehr davon ausgehen, dass das Objekt nicht mehr gebraucht wird. Wo ist also die eigentliche Idee hinter den RValue-Referenzen hin?

    Sollte man stattdessen im Beispiel std::forward benutzen? (den Unterschied zwischen std::forward und std::move habe ich nie so richtig verstanden, beide machen doch aus ihrem Argument eine RValue - oder nicht?)
    Oder ist das T&& eher gedacht für "mir ist egal, was du mir gibst, Hauptsache auf möglichst performante Weise und ich möchte nicht umständlich überladen, um R- und LValue zu unterscheiden" und wenn man t wirklich zerstören will (es also ein "echter" RValue sein soll) man eine getrennte Überladung für T& anbieten soll (oder mit =delete entfernen)? Dann erschliest sich mir aber gerade nicht, warum T&& besser als const T& ist.

    Wahrscheinlich sind die Antworten auf diese Fragen ganz einfach. Jedoch habe ich bisher im Internet nur teils wiedersprüchliche Erklärungen gefunden. Es wäre also nett, wenn du mir mal die Augen öffnen könntest. 🙂



  • Nur leider darf es ja eben nicht "zerstört" werden, wenn eigentlich ein LValue übergeben wurde.
    Ich darf also trotz RValue-Referenz nicht mehr davon ausgehen, dass das Objekt nicht mehr gebraucht wird. Wo ist also die eigentliche Idee hinter den RValue-Referenzen hin?

    Ich habe das so verstanden, dass man einen LValue übergibt, wenn er sonst nirgends mehr verwindet wird. Somit kann der LValue auch zerstört werden.

    Wie hier beschrieben:

    Moving From Lvalues

    All these move optimizations have one thing in common: they occur when we’re through using the source object. Sometimes, though, we need to give the compiler a hint. For example:

    void g(X);
    
    void f()
    {
        X b;
        g(b);
        …
        g(b);
    }
    

    In line 8, we call g with an lvalue, which is ineligible for resource-stealing—even though we’re never going to use b again. To tell the compiler that it can move from b, we can pass it through std::move:

    void g(X);

    void f()
    {
        X b;
        g(b);              // still need the value of b
        …
        g( std::move(b) ); // all done with b now; grant permission to move
    }
    

    Note that std::move doesn’t itself do any moving. It merely converts its argument into an rvalue reference so that move optimizations can kick in if it is used in a “move-optimized” context. When you see std::move, you should think: “grant permission to move. You can also think of std::move(a) as a descriptive way to write static_cast<X&&>(a).

    Ich hoffe ich habe das richtig verstanden.



  • ipsec schrieb:

    Was mich jetzt noch zu dem "reference collapsing" interessiert:

    template<class T>
    void f(T&& t)   //<-- aha, ich nehme einen RValue, also ein temporäres Objekt
    {
        T t2(std::move(t));   //<-- also darf es "zerstört" werden
    }
    

    Nur leider darf es ja eben nicht "zerstört" werden, wenn eigentlich ein LValue übergeben wurde.

    Wegen der Template-Argument-Deduktionsregel und dem Reference-Collapsing kann aus t eben auch eine Lvalue-Referenz werden! Wenn Du T&& als Parameter benutzt und T wird deduziert ist das quasi ein "Ich nehm alles"-Parameter. Rvalue-Referenzen binden immer noch nur Rvalues, aber t muss hier keine Rvalue-Referenz sein. Die Information, ob der Aufrufer uns ein Lvalue oder Rvalue gegeben hat steckt sozusagen in dem Typ T. Und diese Wert-Kategorie kann mit std::forward wieder hergestellt werden:

    template<class T>
    void f(T&& t)   //<-- "Ich nehme alles", Lvalues und Rvalues
    {
        T t2(std::forward<T>(t));
    }
    

    std::forward hat den Return-Typen T&&. Es liefert also entweder eine unbenannte Lvalue-Referenz (wenn T eine Lvalue-Referenz ist) oder eine Rvalue-Referenz (sonst). Dementsprechend wird möglicherweise ein Move-Konstruktor aufgerufen, falls Du f mit einem Rvalue aufrufst und mit einem Copy-Konstruktor sonst. Das ganze nennt man jetzt "Perfect Forwarding". "Perfekt" deswegen, weil die Lvalue/Rvalue-Eigenschaft des originalen Ausdrucks wieder hergestellt werden kann und der Parameter damit so weitergeleitet werden kann, wie man ihn bekommen hat:

    void foo(int&);  // #1
    void foo(int&&); // #2
    
    template<class T> void bar(T&& x) { foo(std::forward<T>(x)); }
    
    void test() {
      using std::move;
      int i = 5;
      bar(i);       // T=int&, T&&=int&  --> foo #1
      bar(23);      // T=int,  T&&=int&& --> foo #2
      bar(move(i)); // T=int,  T&&=int&& --> foo #2
    }
    

    Lässt Du das forward weg, wird immer #1 aufgerufen, weil x, da eine benannte Referenz, immer ein Lvalue-Ausdruck ist. Ersetzt Du forward durch move, wird immer #2 aufgerufen.

    Auf der einen Seite ist die Sonderregel der Template-Argument-Dedutkions praktisch, weil sie "Perfect Forwarding" erlaubt. Auf der anderen Seite kann es eine böse Überraschung geben, wenn man nicht darauf achtet:

    template<class T> void dings(T const&); // #1
    template<class T> void dings(T&&);      // #2
    
    void test2() {
      int i = 99;
      dings(i); // ruft dings #2 auf !!!
      // weil Parameter-Typ "int&" ein besserer Match als "int const&" ist.
    }
    

    ipsec schrieb:

    Sollte man stattdessen im Beispiel std::forward benutzen? (den Unterschied zwischen std::forward und std::move habe ich nie so richtig verstanden, beide machen doch aus ihrem Argument eine RValue - oder nicht?)

    Ob Du std::forward verwenden solltest, hängt davon ab, was Die Funktion machen soll. Der Unterschied zwischen move und forward ist folgender: move liefert immer eine Rvalue-Referenz. forward muss immer mit einem explizit angegebenen Template-Parameter aufgerufen werden. In Abhängigkeit dieses Template-Parameters liefert forward entweder eine Lvalue-Referenz oder Rvalue-Referenz und kann damit die "Wertigkeit" des originalen Ausdrucks wieder herstellen.

    ipsec schrieb:

    Oder ist das T&& eher gedacht für "mir ist egal, was du mir gibst,

    Wenn T ein Template-Paarameter des Funktionstemplate ist und Du T&& als Funktionsparametertyp verwendest ist das ein "Ich fange alles und merke mir über den Typ T die originale Wertigkeit"-Parameter.

    ipsec schrieb:

    Dann erschliest sich mir aber gerade nicht, warum T&& besser als const T& ist.

    Dich zwingt ja keiner. Es kommt darauf an, was Du machen willst.

    kk



  • Vielen Dank, das war sehr erleuchtend!
    Kurz: T&& kann genauso wie const T& "alles" nehmen, ermöglicht aber Perfect Forwarding (weil man anhand von T zwischen L- und RValue unterscheiden kann). Nun verstehe ich endlich auch den Sinn von std::forward und warum man bei diesem explizit den Typ angeben muss.



  • ScyllaIllciz schrieb:

    Nur leider darf es ja eben nicht "zerstört" werden, wenn eigentlich ein LValue übergeben wurde.
    Ich darf also trotz RValue-Referenz nicht mehr davon ausgehen, dass das Objekt nicht mehr gebraucht wird. Wo ist also die eigentliche Idee hinter den RValue-Referenzen hin?

    Ich habe das so verstanden, dass man einen LValue übergibt, wenn er sonst nirgends mehr verwindet wird. Somit kann der LValue auch zerstört werden.

    Andersherum. Ein Name (in einem Ausdruck), der sich auf ein Objekt bezieht -- das schließt auch benannte Referenzen ein -- ist immer ein Lvalue-Ausdruck. Temporäre Objekte haben keinen Namen. Ein Funktionsaufruf, welcher per Wert einen vector<int> zurück gibt ist ein Rvalue-Ausdruck. Ein Funktionsaufruf, der eine Lvalue-Referenz zurück gibt ist natürlich auch ein Lvalue-Ausdruck. Ein Lvalue-Ausdruck bezieht sich immer auf ein Objekt. Ein Rvalue-Ausdruck muss sich nicht unbedingt auf ein Objekt im Sinne von C++ beziehen. Rvalues von skalaren Typen (int,double,void*) sind keine Objekte, sondern einfach nur Werte.

    Wenn man jetzt also schon zwischen Lvalues und Rvalues mit diesen zwei Referenztypen unterscheiden kann und durch das "Klauen von Resourcen" bei Rvalues unnötiges Kopieren vermeiden kann, ohne dass es jemanden stören würde, dann brauchen wir auch eine Syntax, um explizit sagen zu können, dass uns der Erhalt des Zustands eines bestimmten Objekts nicht mehr wichtig ist. Dafür ist std::move da.

    ScyllaIllciz schrieb:

    Wie hier beschrieben:

    Moving From Lvalues

    Dieser Abschnitt bezieht sich genau darauf, wie manexplizit aus Lvalues Rvalues machen kann, um zB unnötiges Kopieren zu vermeiden.

    kk


Anmelden zum Antworten