verständnisproblem rvalue references



  • ich hab mich lange zeit vor den rvalue references gedrückt weil ich dachte ich komme auch mit den guten alten referenzen aus. eben dachte ich irgendwie dass ich sie zwingend einsetzen muss (nicht sicher). ich wollte meine bildungslücke endlich schließen aber egal welche erklärung, welchen stackoverflow thread, welchen blog ich lese, der autor verliert mich immer spätestens wenn er std::move einführt. gibt es irgendwo eine erklärung für idioten? und ist der code korrekt?

    struct foo
    {
    	std::function<int(int)> f;
    	foo(const std::function<int(int)>& f)
    		: f(f)
    	{
    	}
    	foo(std::function<int(int)>&& f)
    		: f(std::forward(f))
    	{
    	}
    };
    


  • Rvalue-referenzen sind wie die 'normalen', lvalue-referenzen, nur binden die rvr eher an rvalues, dh temporäre objekte und rückgabeobjekte von funktionen und lvr an die anderen, wenn mehrere funktionsüberladungen infrage kommen und diese sich nur anhand der referenzart unterscheiden.
    Bei deinem beispiel wird Konstruktor #2 aufgerufen, wenn man direkt den Rückgabewert einer funktion weitergibt oder man ein lvalue oder lvr in eine rvr wandelt, zb durch casten. Genau dieses casten macht std::move und auch nichts anderes.



  • roflo schrieb:

    oder [wenn] man ein lvalue oder lvr in eine rvr wandelt, zb durch casten. Genau dieses casten macht std::move und auch nichts anderes.

    und was bringt das? was bewerkstelligt man damit? ich kann mir das überhaupt nicht vorstellen mit "lv in rvr umwandeln"...

    außerdem: ist mein beispiel so sinnvoll oder würde man das anders machen? was ist der nachteil dazu nur den ersten oder nur den zweiten ctor anzubieten?



  • torenfroll schrieb:

    außerdem: ist mein beispiel so sinnvoll oder würde man das anders machen? was ist der nachteil dazu nur den ersten oder nur den zweiten ctor anzubieten?

    Statt std::forward solltest du std::move schreiben (compiliert das so überhaupt?), schließlich ist das hier kein Fall von Perfect Forwarding. Wenn du nur den ersten Konstruktor anbietest wird das std::function Objekt möglicherweise häufiger kopiert als es nötig gewesen wäre. Wenn du nur die zweite Variante anbietest kann man nur temporäre std::function Objekte oder welche die man mit std::move zu einem rvalue gemacht hat in den Konstruktor packen.



  • sebi707 schrieb:

    Statt std::forward solltest du std::move schreiben (compiliert das so überhaupt?), schließlich ist das hier kein Fall von Perfect Forwarding.

    nein, es kompiliert in der tat nicht, ich habs eben getestet. aber wieso ist das nun kein fall von perfect forwarding?

    sebi707 schrieb:

    Wenn du nur den ersten Konstruktor anbietest wird das std::function Objekt möglicherweise häufiger kopiert als es nötig gewesen wäre. Wenn du nur die zweite Variante anbietest kann man nur temporäre std::function Objekte oder welche die man mit std::move zu einem rvalue gemacht hat in den Konstruktor packen.

    heisst das, ich muss für alle setter, alle ctoren, usw immer 2 versionen anbieten, 1x mit const& und 1x mit &&? und das muss ich auch nachholen für den gesammten bislang geschriebenen code?



  • torenfroll schrieb:

    heisst das, ich muss für alle setter, alle ctoren, usw immer 2 versionen anbieten, 1x mit const& und 1x mit &&? und das muss ich auch nachholen für den gesammten bislang geschriebenen code?

    Nein!



  • manni66 schrieb:

    torenfroll schrieb:

    heisst das, ich muss für alle setter, alle ctoren, usw immer 2 versionen anbieten, 1x mit const& und 1x mit &&? und das muss ich auch nachholen für den gesammten bislang geschriebenen code?

    Nein!

    ...sondern?



  • torenfroll schrieb:

    aber wieso ist das nun kein fall von perfect forwarding?

    Perfect Forwarding sieht leider ziemlich ähnlich aus, da es auch && nutzt. Man kann es aber unterscheiden wenn der Typ auf den sich && bezieht ein Template Parameter ist:

    template <class T>
    foo(T&& x)  // Perfect forwarding
    {
      bar(std::forward<T>(x));
    }
    

    torenfroll schrieb:

    ...sondern?

    Man muss es nicht übertreiben. Im Normalfall würde ich weiter const& nehmen außer ich verspreche mir durch rvalues einen Performance Gewinn. Den gibts aber auch nur bei Klassen die Speicher auf dem Heap haben. Also ein großes struct voller ints wird dadurch auch nicht schneller kopiert. Wenn man meint durch rvalues kann man einige kritische Kopien einsparen kann man man über entsprechende Setter nachdenken. Für Konstruktoren bietet sich auch sowas an:

    struct foo
    {
        std::function<int(int)> f;
        foo(std::function<int(int)> f)  // Call by value
            : f(std::move(f))
        {
        }
    };
    

    Diese Variante ist ziemlich optimal für lvalues und rvales. Und man braucht nur eine Funktion was gerade bei mehreren Parametern hilft (sonst braucht man 2^n Funktionen wenn man jeden Parameter als const& und && haben möchte).


Log in to reply