Move Semantics
lvalues vs rvalues
lvalue | rvalue | |
|---|---|---|
Definition | Has a name and a stable address; persists beyond the current expression | A temporary with no persistent name; about to be destroyed |
Examples | A variable like x, a dereferenced pointer *p, a container element v[0] | A literal like 42, the result of a + b, a function returning by value |
Can you take its address? | Yes, with &x | Generally no |
Typical fate | Continues to exist after the statement | Destroyed at the end of the full expression |
std::move does not move anything
A performance-motivated example
Returning and relocating a large vector without copying
#include <iostream>
#include <vector>
// Building a large vector locally and returning it by value.
// The compiler applies (guaranteed, since C++17) copy elision here,
// and even without elision, this would be a move, not a deep copy.
std::vector<int> buildLargeVector(std::size_t n) {
std::vector<int> data;
data.reserve(n);
for (std::size_t i = 0; i < n; ++i) data.push_back(static_cast<int>(i));
return data; // moved (or elided) out, never deep-copied
}
int main() {
std::vector<int> a = buildLargeVector(1'000'000);
// Moving 'a' into another container: std::move casts 'a' to an
// rvalue reference, so the vector move constructor runs — it
// steals a's internal buffer pointer instead of copying 1M ints.
std::vector<std::vector<int>> allVectors;
allVectors.push_back(std::move(a));
// 'a' is now in a valid but unspecified state (typically empty).
std::cout << "a.size() after move: " << a.size() << "\n";
std::cout << "allVectors[0].size(): " << allVectors[0].size() << "\n";
return 0;
}lvalues have a name and persist; rvalues are temporaries about to be destroyed.
Copying from a temporary is wasted work — moving steals its resources instead.
std::move performs no action itself; it is only a cast enabling move overloads to be selected.
Returning large objects by value and moving them into containers are common places move semantics pays off.