But don’t apply std::move or std::forward to local objects if they would otherwise be eligible for the return value optimization.
Normal cases
When forwarding them to other functions, rvalue references, which is always bound to rvalues, should be unconditionally cast to rvalues (via std::move), while universal references, which is sometimes bound be rvalues, should be conditionally cast to rvalues (via std::forward):
|
|
What if
You may wonder, what will happen if we exchange std::forward ans std::move:
-
applying
std::forwardon rvalue references can exhibit the proper behavior, but the source code is wordy, error-prone1, and unidiomatic:1 2 3 4Widget::Widget(Widget&& rhs) : name(std::forward<std::string>(rhs.name)), p(std::forward<std::shared_ptr<SomeDataStructure>>(rhs.p)) {...} -
using
std::moveon universal refenreces can have the effect of unexpectedly modifying lvalues (e.g., local variables):1 2 3 4 5 6 7 8 9template<typename T> void Widget::setName(T&& newName) { name = std::move(newName); } // compiles, but is bad! std::string getWidgetName(); // factory function Widget w; auto n = getWidgetName(); // n is local variable w.setName(n); // moves n into w ... // n's value now unkown!
Another alternative is to replace the template taking a universal reference with a pair of functions overloaded on lvalue references and rvalue references:
|
|
The cost we pay for this replacement is:
-
More source code to write and maintain (two functions instead of a single template)
-
Less efficient in some cases such as this2:
1w.setName("John Smith"); -
Poor scalability of the design if more parameters come (each of which can be an lvalue or rvalue):
1 2tempalte<class T, class... Args> shared_ptr<T> make_shared(Args&&... args); // can't overload on lvalues and rvalues on args. universal reference is used and std::forward is applyied
Other usage
Moreove, sometimes we want to apply std::move or std::forward to only the final use of the reference when an rvalue reference or a universal reference will be used more than once in a single function:
|
|
The same logic applies to std::move, except that in rare cases, we want to call std::move_if_noexcept instead of std::move (refer to EMCpp item 14).
If a function returns be value, and the returning object is bound to an rvalue refernece or a universal reference, we also want to apply std::move or std::forward to support potential move construction and get more efficient:
|
|
When not to
According to Standardization Committee, there’s a kind of optimizatoin called return value optimization (RVO):
the “copying” version of makeWidget can avoid the need to copy the local variable w by constructing it in the memory alloted for the function’s return value
Due to this optimization, we should not use std::move on a local object being returnd from a function that’s returning by value so that we won’t precluding the RVO that compilers will do for the return value.
-
The type we pass to
std::forwardshould be a non-reference, according to the convention for encoding that the argument being passed is an rvalue (see EMCpp Item 28). ↩︎ -
With the universal reference version, there’s only one call to
std::stringassignment operator forw.name, since the string literal would be passed to this function as aconst char*pointer; the overloaded versions entail execution of onestd::stringconstructor (to create a temporary), onestd::stringmove assignment operator (to movenewNameintow.name), and onestd::stringdestructor (to destroy the temporary), which is almost certainly more expensive. ↩︎