61 Intermediate The Hundred
return std::move(x) Is Slower
a helping hand the optimizer never asked for
struct Widget {
Widget() = default;
Widget(const Widget&) { std::cout << "copy\n"; }
Widget(Widget&&) { std::cout << "move\n"; }
~Widget() {}
};
Widget make_simple() {
Widget w;
return w;
}
Widget make_optimized() {
Widget w;
return std::move(w); // squeeze out that last copy
}
int main() {
std::cout << "make_simple:\n";
Widget a = make_simple();
std::cout << "make_optimized:\n";
Widget b = make_optimized();
}
Run it. What does each call print?
Answer
make_simple prints nothing — no copy, no move. make_optimized, the one that tries to help, prints move.
Why¶
return w; makes w eligible for the named return value optimization: the compiler
builds w directly in the caller's a, so no copy or move constructor ever runs. NRVO
is technically optional, but every mainstream compiler does it — GCC does it here even
at -O0. The elision rule requires the return expression to be the plain name of a
local; std::move(w) is a function call, not a name, so elision is off the table and
the move constructor must run. You paid a move to dodge a copy that could never have
happened anyway — a returned local is treated as an rvalue, so the fallback is a move.
GCC flags it with -Wpessimizing-move (part of -Wall): "moving a local object in a
return statement prevents copy elision" — and even suggests removing the call.
The fix¶
Since C++17, returning a prvalue (return Widget{};) is even guaranteed copy-free:
elision there is mandatory, not an optimization.
Takeaway: std::move on a return value is a pessimization — return local; is
already optimal.
Try it: g++ -std=c++17 main.cpp -o demo && ./demo