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::forward
on rvalue references can exhibit the proper behavior, but the source code is wordy, error-prone1, and unidiomatic:1 2 3 4
Widget::Widget(Widget&& rhs) : name(std::forward<std::string>(rhs.name)), p(std::forward<std::shared_ptr<SomeDataStructure>>(rhs.p)) {...}
-
using
std::move
on universal refenreces can have the effect of unexpectedly modifying lvalues (e.g., local variables):1 2 3 4 5 6 7 8 9
template<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:
1
w.setName("John Smith");
-
Poor scalability of the design if more parameters come (each of which can be an lvalue or rvalue):
1 2
tempalte<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::forward
should 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::string
assignment operator forw.name
, since the string literal would be passed to this function as aconst char*
pointer; the overloaded versions entail execution of onestd::string
constructor (to create a temporary), onestd::string
move assignment operator (to movenewName
intow.name
), and onestd::string
destructor (to destroy the temporary), which is almost certainly more expensive. ↩︎