Move Semantics in Depth
Stealing resources instead of copying them - and doing it safely
std::move, the compiler can pick the cheap move path over the expensive copy path. Used well, move semantics turn O(n) deep copies into O(1) pointer swaps - with no change to how calling code reads.
- read value categories and know exactly when an rvalue reference
T&&binds; - write a correct move constructor and move assignment operator;
- explain why marking them
noexceptchanges program performance, not just its promises; - apply the rule of five (and prefer the rule of zero);
- reason about the moved-from state without triggering undefined behavior.
1. Value categories and rvalue references
Every expression in C++ has both a type and a value category. For everyday move semantics the practical split is between lvalues - things with a name and a stable address you could take with & - and rvalues - temporaries and other objects whose resources are safe to cannibalize because no one will observe them again.
An rvalue reference, written T&&, is a reference that binds only to rvalues. That is the whole trick: an overload taking T&& is selected precisely when the argument is an expiring value, so inside that overload you know it is safe to steal.
void f(const std::string& s); // lvalue overload: binds to anything, may not steal
void f(std::string&& s); // rvalue overload: binds to temporaries, may steal
std::string name = "Ada";
f(name); // calls f(const std::string&) - name is an lvalue
f("Ada"); // calls f(std::string&&) - the temporary is an rvalue
f(std::move(name)); // calls f(std::string&&) - we PROMISE not to need name
std::move does not move anything. It is a cast: it produces an rvalue reference to its argument so that a move overload is chosen. The actual work happens inside the move constructor or move assignment that gets selected. Think of std::move as saying "treat this as expiring" - the moving is done by the type, not by the cast.
void g(std::string&& s), the parameter s has a name, so to move out of it you must write std::move(s) again. Forgetting this silently makes a copy.