lhs += rhs;
return lhs; // copy lhs into
} // return value
the fact that lhs
is an lvalue would force compilers to instead copy it into the return value location. Assuming that the Matrix
type supports move construction, which is more efficient than copy construction, using std::move
in the return
statement yields more efficient code.
If Matrix
does not support moving, casting it to an rvalue won't hurt, because the rvalue will simply be copied by Matrix
's copy constructor (see Item 23). If Matrix
is later revised to support moving, operator+
will automatically benefit the next time it is compiled. That being the case, there's nothing to be lost (and possibly much to be gained) by applying std::move
to rvalue references being returned from functions that return by value.
The situation is similar for universal references and std::forward
. Consider a function template reduceAndCopy
that takes a possibly unreduced Fraction
object, reduces it, and then returns a copy of the reduced value. If the original object is an rvalue, its value should be moved into the return value (thus avoiding the expense of making a copy), but if the original is an lvalue, an actual copy must be created. Hence:
template
Fraction // by-value return
reduceAndCopy( T&&frac) // universal reference param
{
frac.reduce();
return std::forward(frac); // move rvalue into return
} // value, copy lvalue
If the call to std::forward
were omitted, frac
would be unconditionally copied into reduceAndCopy
's return value.
Some programmers take the information above and try to extend it to situations where it doesn't apply. “If using std::move
on an rvalue reference parameter being copied into a return value turns a copy construction into a move construction,” they reason, “I can perform the same optimization on local variables that I'm returning.” In other words, they figure that given a function returning a local variable by value, such as this,
Widget makeWidget() // "Copying" version of makeWidget
{
Widget w; // local variable
… // configure w
return w; // "copy" w into return value
}
they can “optimize” it by turning the “copy” into a move:
Widget makeWidget() // Moving version of makeWidget
{
Widget w;
…
return std::move(w);// move w into return value
} // ( don't do this! )
My liberal use of quotation marks should tip you off that this line of reasoning is flawed. But why is it flawed?
It's flawed, because the Standardization Committee is way ahead of such programmers when it comes to this kind of optimization. It was recognized long ago that 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. This is known as the return value optimization (RVO), and it's been expressly blessed by the C++ Standard for as long as there's been one.
Wording such a blessing is finicky business, because you want to permit such copy elision only in places where it won't affect the observable behavior of the software. Paraphrasing the legalistic (arguably toxic) prose of the Standard, this particular blessing says that compilers may elide the copying (or moving) of a local object [12] Eligible local objects include most local variables (such as w inside makeWidget ) as well as temporary objects created as part of a return statement. Function parameters don't qualify. Some people draw a distinction between application of the RVO to named and unnamed (i.e., temporary) local objects, limiting the term RVO to unnamed objects and calling its application to named objects the named return value optimization (NRVO).
in a function that returns by value if (1) the type of the local object is the same as that returned by the function and (2) the local object is what's being returned. With that in mind, look again at the “copying” version of makeWidget
:
Widget makeWidget() // "Copying" version of makeWidget
{
Widget w;
…
return w; // "copy" w into return value
}
Both conditions are fulfilled here, and you can trust me when I tell you that for this code, every decent C++ compiler will employ the RVO to avoid copying w
. That means that the “copying” version of makeWidget
doesn't, in fact, copy anything.
The moving version of makeWidget
does just what its name says it does (assuming Widget
offers a move constructor): it moves the contents of w
into makeWidget
's return value location. But why don't compilers use the RVO to eliminate the move, again constructing w
in the memory alloted for the function's return value? The answer is simple: they can't. Condition (2) stipulates that the RVO may be performed only if what's being returned is a local object, but that's not what the moving version of makeWidget
is doing. Look again at its return
statement:
return std::move(w);
What's being returned here isn't the local object w
, it's a reference to w
— the result of std::move(w)
. Returning a reference to a local object doesn't satisfy the conditions required for the RVO, so compilers must move w
into the function's return value location. Developers trying to help their compilers optimize by applying std::move
to a local variable that's being returned are actually limiting the optimization options available to their compilers!
But the RVO is an optimization. Compilers aren't required to elide copy and move operations, even when they're permitted to. Maybe you're paranoid, and you worry that your compilers will punish you with copy operations, just because they can. Or perhaps you're insightful enough to recognize that there are cases where the RVO is difficult for compilers to implement, e.g., when different control paths in a function return different local variables. (Compilers would have to generate code to construct the appropriate local variable in the memory allotted for the function's return value, but how could compilers determine which local variable would be appropriate?) If so, you might be willing to pay the price of a move as insurance against the cost of a copy. That is, you might still think it's reasonable to apply std::move
to a local object you're returning, simply because you'd rest easy knowing you'd never pay for a copy.
In that case, applying std::move
to a local object would still be a bad idea. The part of the Standard blessing the RVO goes on to say that if the conditions for the RVO are met, but compilers choose not to perform copy elision, the object being returned must be treated as an rvalue . In effect, the Standard requires that when the RVO is permitted, either copy elision takes place or std::move
is implicitly applied to local objects being returned. So in the “copying” version of makeWidget
,
Читать дальше