Code-Duplication durch RValue-Referenzen // perfect forwarding ohne templates



  • Kopierst du im ersten Code in Zeile 5 nicht eh?

    Edit:
    Ich verstehe somit deine Kritik nicht so ganz. Wenn der Aufrufer nicht moven kann, musst du doch eh kopieren. Dann steht also move + copy vs. copy. Wenn der Aufrufer moven kann, hast du 2x move, also das, was angestrebt wird.



  • Wie wärs mit:

    void InterfaceEventQueue::pushEvent(InterfaceEvent const& evt)
    {
        pushEvent(InterfaceEvent(evt));
    }
    


  • @PI
    Sieht cool aus, aber wie vermeidet das eine Kopie?



  • Da wird 1 mal kopiert und 1 mal gemoved.



  • 314159265358979 schrieb:

    Da wird 1 mal kopiert und 1 mal gemoved.

    Ich würde eher sagen damit wird einmal kopiert und nichts gemoved. Aber abgesehen davon war das Ziel doch, die Möglichkeit zu haben nicht zu kopieren?



  • Hier wird ein temporärer Wert erzeugt, mit diesem wird dann die move-Version aufgerufen, die den Wert in die Queue "hineinmoved". Eine Kopie wird sich nicht vermeiden lassen, dazu ist die Copy-Version schließlich da.

    Edit: Ach ne, vergesst das wieder. Kommt aufs selbe raus wie cooky's Version, nur mit expliziter Kopie.



  • Hä? Habe ich das Problem irgendwie nicht verstanden? Ich formulier's mal so, wie ich es verstanden habe:
    Es soll zwei möglichkeiten geben, etwas über eine Funktion in die nächste zu geben: Kopieren oder Moven.
    Jetzt hat man aber quasi zwei mal den Code, weil man je eine Funktion zum Moven und eine zum Kopieren braucht. Lösung:
    Man erwartet gleich einen Value weil man davon ausgeht, dass "move" quasi gratis ist. Jetzt kann der Aufrufer sich entscheiden, ob er das übergebene Objekt noch braucht, oder nicht.
    Wie passt da dein Vorschlag mit der const& rein, ich raffs nicht? Da brauchst du dann doch immer noch eine zweite Funktion zum Moven?



  • pumuckl schrieb:

    Wie meinst du? InterfaceEventQueue::pushEvent(InterfaceEvent evt) ? Dann kompierst du doch auf jeden Fall?

    Nein, wenn InterfaceEvent movable ist, kann bei RValues der Move-Konstruktor angewandt werden. Und LValues machst du mit std::move() zu RValues. Und konstante LValues kopierst du...



  • Hm...

    void pushEvent(InterfaceEvent&& e)
    {
        pushEventImpl(std::move(e));
    }
    
    void pushEvent(InterfaceEvent const& e)
    {
        pushEventImpl(e);
    }
    
    template <typename T>
    void pushEventImpl(T&& arg)
    {
        boost::mutex::scoped_lock lock(the_mutex);
        evtQueue.emplace(std::forward<T>(arg));
        lock.unlock();
        the_condition_variable.notify_one(); 
    }
    

    Kosten bei Copy-Version: 1 Kopie
    Kosten bei Move-Version: 1 Move



  • Nexus schrieb:

    pumuckl schrieb:

    Wie meinst du? InterfaceEventQueue::pushEvent(InterfaceEvent evt) ? Dann kompierst du doch auf jeden Fall?

    Nein, wenn InterfaceEvent movable ist, kann bei RValues der Move-Konstruktor angewandt werden. Und LValues machst du mit std::move() zu RValues. Und konstante LValues kopierst du...

    Stimmt auffallend, danke euch. Ich habe dann allerdings in allen Fällen den Aufwand des Move-Ctors gegenüber der Alternative mit den zwei Überladungen by R-Ref/const-L-Ref. Wobei der eine move-ctor wieder wegoptimiert werden kann.

    Das pauschale "take arguments by const ref" von früher zählt also nicht mehr für moveable Klassen, wenn die per copy/move weitergereicht werden.



  • Wie gross ist dein InterfaceEvent in Byte?



  • Erscheint es euch eigentlich auch etwas doof, dass man "echtes" perfect Forwarding nur durch diese komische Sonderregel mit Templates bekommt? Was soll das?



  • knivil schrieb:

    Wie gross ist dein InterfaceEvent in Byte?

    Ein enum und ein unique_ptr, also irgendwas um die 16 byte auf x64. Mir gehts nur um den allgemeinen Fall, wo man auch mal mit größeren Movables zu tun hat.



  • pumuckl schrieb:

    Das pauschale "take arguments by const ref" von früher zählt also nicht mehr für moveable Klassen, wenn die per copy/move weitergereicht werden.

    Mein Reden!

    Wenn man vom Typ weiß, dass er einen effizienten move-ctor hat, darf man in dieser Situation wieder pass-by-value verwenden. Im Idealfall kann der Compiler hier auch ein unnötiges Move wegoptimieren (move elision), so dass es keinen Laufzeitunterschied macht. Das klappt zwar nicht immer, aber so schlimm wäre es ohne move elision auch nicht.

    class person {
    public:
      explicit person(string name) : name_(move(name)) {}
    private:
      string name_;
    };
    

    Bei generischem Code sieht das aber wieder etwas anders aus, siehe z.B. vector<>::push_back. Dort findet wieder die Überladung mit &&/const& statt. Allerdings kann man dort versuchen, wenigstens den Schreibaufwand zu reduzieren:

    template<class T, class Alloc>
    class vector v {
      ::
      void push_back(T const& x) {emplace_back(     x );}
      void push_back(T     && x) {emplace_back(move(x));}
    
      template<class...Args>
      void emplace_back(Args&&...args) {...}
      ::
    };
    

Anmelden zum Antworten