RValue Refs, std::move, best practice



  • Hi,

    in einem Thread von mir gab es eine ganze Reihe von Antworten, die viele Details dazu geben, was man in welchem Fall optimal einsetzen sollte. Vielleicht hilft Dir das 🙂
    http://www.c-plusplus.net/forum/314315



  • knivil schrieb:

    Variante 1: Weicht man diese Anforderung auf, so waere es gut, zwei Methoden anzubieten:

    void post(T const& msg) ... // copy messages
    void post(T&& msg) ... // move messages
    

    Welche Methode wird bei temporaeren Objekten aufgerufen?

    Bei Temporären Variablen wird natürlich die zweite Variante aufgerufen.

    Variante 2: Da es wie schon gesagt fuer mehr Parameter recht aufwendig wird, sollte nur eine Methode angeboten werden:

    void post(T msg)
    

    Und es wird dem Aufrufer ueberlassen, zu verschieben oder zu kopieren:

    q.post(my_ms); // copy message
    q.post(std::move(my_msg)); // move message
    

    Prinzipiell gefaellt mir der Gedanke, den Benutzer entscheiden zu lassen, ob kpiert werden soll

    Meine Fragen: Ist Variante 2 "best practice"? Gibt es Nachteile? Gibt es weitere Vorteile?

    imho ist Variante 2 best practice. Nachteil ist, dass die unter Umständen nicht ganz so effizient sein kann.
    Kosten Variante 1:
    LValue: 1 copy (Argument zum Ziel).
    RValue: 1 move (Argument zum Ziel).
    Kosten Variante 2:
    LValue: 1 copy (Quelle zum Argument), 1 move (Argument zum Ziel)
    RValue: 2 move (Temporary zum Argument, Argument zum Ziel)*

    (Wenn man natürlich aus dem LValue ein RValue macht, ist das effizienter.)

    *Ideone überrascht mich da aber: http://ideone.com/GtERzZ
    Kann es sein, dass der Compiler das Temporary direkt im Argument erstellt?
    Wenn ja, ist diese Variante genauso gut, wie die mit T&&.



  • Best Practise waere, beides anzubieten. Die Standardcontainer bieten auch beides an. Das heisst aber nicht, dass man nicht beides ueber die selbe Funktion implementieren kann:

    template <typename T>
    void post_impl(T&& msg)
    {
        std::lock_guard<std::mutex> lock(mtx);
        queue.push_front(std::forward<T>(msg));
        cond.notify_one();
    }
    
    void post(T const& msg)
    {
        post_impl(msg);
    }
    
    void post(T&& msg)
    {
        post_impl(std::move(msg));
    }
    


  • Kellerautomat schrieb:

    Best Practise waere, beides anzubieten. Die Standardcontainer bieten auch beides an.

    Klar, in diesem Fall wäre es das, da es nur ein Argument gibt.
    Hat man aber mehrere ist die Arbeit für den Programmierer doch etwas hoch.
    Ich hab mal eine Klasse gesehen, die das Argument entweder als LValue oder RValue Referenz speichert, jenachdem was es ist. Das ermöglicht dann effiziente Übergabe an die entsprechenden Copy/Movectors.



  • Nathan schrieb:

    *Ideone überrascht mich da aber: http://ideone.com/GtERzZ
    Kann es sein, dass der Compiler das Temporary direkt im Argument erstellt?

    Wenn ich das richtig gelesen habe, ist es in der Tat so, dass Compiler nicht benötigte Kopien/Moves in Argumente komplett weg optimieren können, ähnlich wie beim Rückgabewert.

    Nathan schrieb:

    Wenn ja, ist diese Variante genauso gut, wie die mit T&&.

    Fast, denn das weg optimieren geht wohl nur bei echten Temporaries und nicht bei std::move.



  • Kellerautomats Code mit richtigen Bezeichnern:

    template <typename... T>
    void emplace(T&&... msg)
    {
        std::lock_guard<std::mutex> lock(mtx);
        queue.emplace_front(std::forward<T>(msg)...);
        cond.notify_one();
    }
    
    void post(T const& msg)
    {
        emplace(msg);
    }
    
    void post(T&& msg)
    {
        emplace(std::move(msg));
    }
    


  • Nathan schrieb:

    Ich hab mal eine Klasse gesehen, die das Argument entweder als LValue oder RValue Referenz speichert, jenachdem was es ist. Das ermöglicht dann effiziente Übergabe an die entsprechenden Copy/Movectors.

    Hier sowas in der Art meinte ich: http://ideone.com/fTORB3
    Es kommen zwar zustätzliche Kosten (if Abfrage und ein weitere Def-Ctor), aber das ist in der Regel billig. Und man hat somit das beste von beiden Seiten: genau die Aufrufe, die man haben will, und wenig Tipparbeit.



  • Nathan schrieb:

    Hier sowas in der Art meinte ich: http://ideone.com/fTORB3
    Es kommen zwar zustätzliche Kosten (if Abfrage und ein weitere Def-Ctor), aber das ist in der Regel billig. Und man hat somit das beste von beiden Seiten: genau die Aufrufe, die man haben will, und wenig Tipparbeit.

    😋 👍



  • Hier noch eine verbesserte Version, die auch mit Konvertierungen klar kommt und keinen zustätzlichen Overhead (abgesehen vom if-else und dem Erzeugen das fast_arg) bietet: http://ideone.com/uIIPMi



  • emplac0r schrieb:

    Kellerautomats Code mit richtigen Bezeichnern:

    template <typename... T>
    void emplace(T&&... msg)
    {
        std::lock_guard<std::mutex> lock(mtx);
        queue.emplace_front(std::forward<T>(msg)...);
        cond.notify_one();
    }
    
    void post(T const& msg)
    {
        emplace(msg);
    }
    
    void post(T&& msg)
    {
        emplace(std::move(msg));
    }
    

    👍



  • Warum nicht einfach so?

    template <typename U>
    void post(U &&msg)
    {
        std::lock_guard<std::mutex> lock(mtx);
        queue.push_front(std::forward<U>(msg));
        cond.notify_one();
    }
    


  • TyRoXx schrieb:

    Warum nicht einfach so?

    Um es sich nicht mit Stroustrup zu verscherzen.

    Stroustrup schrieb:

    Improved push operations: My favorite container operation is push_back() that allows a container to grow gracefully:

    vector<pair<string,int>> vp;
    	string s;
    	int i;
    	while(cin>>s>>i) vp.push_back({s,i}); // {s,i} geht nur, wenn kein Template
    


  • Danke fuer eure Vorschlaege, std::forward werde ich wohl etwas naeher beleuchten. Aber eine Hilfsklasse wird es nicht geben. Tipparbeit ist fuer mich nie ein Grund.



  • knivil schrieb:

    Danke fuer eure Vorschlaege, std::forward werde ich wohl etwas naeher beleuchten. Aber eine Hilfsklasse wird es nicht geben. Tipparbeit ist fuer mich nie ein Grund.

    Es geht nicht nur um Tipparbeit, sondern auch um Übersichtlichkeit und Wartbarkeit. Bei doppeltem Code kann man zwei Fehler korrigieren müssen oder in einer Implementierung einen Fehler haben, den man schwer findet.


Anmelden zum Antworten