Pass by value vs pass by rvalue reference

前端 未结 7 1004
独厮守ぢ
独厮守ぢ 2020-11-29 01:40

When should I declare my function as:

void foo(Widget w);

as opposed to

void foo(Widget&& w);?

Assume this

7条回答
  •  醉酒成梦
    2020-11-29 01:53

    Unless the type is a move-only type you normally have an option to pass by reference-to-const and it seems arbitrary to make it "not part of the discussion" but I will try.

    I think the choice partly depends on what foo is going to do with the parameter.

    The function needs a local copy

    Let's say Widget is an iterator and you want to implement your own std::next function. next needs its own copy to advance and then return. In this case your choice is something like:

    Widget next(Widget it, int n = 1){
        std::advance(it, n);
        return it;
    }
    

    vs

    Widget next(Widget&& it, int n = 1){
        std::advance(it, n);
        return std::move(it);
    }
    

    I think by-value is better here. From the signature you can see it is taking a copy. If the caller wants to avoid a copy they can do a std::move and guarantee the variable is moved from but they can still pass lvalues if they want to. With pass-by-rvalue-reference the caller cannot guarantee that the variable has been moved from.

    Move-assignment to a copy

    Let's say you have a class WidgetHolder:

    class WidgetHolder {
        Widget widget;
       //...
    };
    

    and you need to implement a setWidget member function. I'm going to assume you already have an overload that takes a reference-to-const:

    WidgetHolder::setWidget(const Widget& w) {
        widget = w;
    }
    

    but after measuring performance you decide you need to optimize for r-values. You have a choice between replacing it with:

    WidgetHolder::setWidget(Widget w) {
        widget = std::move(w);
    }
    

    Or overloading with:

    WidgetHolder::setWidget(Widget&& widget) {
        widget = std::move(w);
    }
    

    This one is a little bit more tricky. It is tempting choose pass-by-value because it accepts both rvalues and lvalues so you don't need two overloads. However it is unconditionally taking a copy so you can't take advantage of any existing capacity in the member variable. The pass by reference-to-const and pass by r-value reference overloads use assignment without taking a copy which might be faster

    Move-construct a copy

    Now lets say you are writing the constructor for WidgetHolder and as before you have already implemented a constructor that takes an reference-to-const:

    WidgetHolder::WidgetHolder(const Widget& w) : widget(w) {
    }
    

    and as before you have measured peformance and decided you need to optimize for rvalues. You have a choice between replacing it with:

    WidgetHolder::WidgetHolder(Widget w) : widget(std::move(w)) {
    }
    

    Or overloading with:

    WidgetHolder::WidgetHolder(Widget&& w) : widget(std:move(w)) {
    }
    

    In this case, the member variable cannot have any existing capacity since this is the constructor. You are move-constucting a copy. Also, constructors often take many parameters so it can be quite a pain to write all the different permutations of overloads to optimize for r-value references. So in this case it is a good idea to use pass-by-value, especially if the constructor takes many such parameters.

    Passing unique_ptr

    With unique_ptr the efficiency concerns are less important given that a move is so cheap and it doesn't have any capacity. More important is expressiveness and correctness. There is a good discussion of how to pass unique_ptr here.

提交回复
热议问题