CppMove Semantics

Move Semantics

Move semantics is the mechanism C++11 introduced to avoid unnecessary deep copies. To understand it, you first need to understand the distinction between two categories of expressions that every C++ value falls into: lvalues and rvalues.
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

The key insight: if you are copying from an rvalue — a temporary that is going to be destroyed the moment the current statement finishes anyway — a deep copy is wasted work. Nobody will ever use that temporary's original resources again, so instead of copying its internal buffer, you can simply steal it: transfer ownership of the pointer/buffer/handle into the new object, and leave the temporary in an empty, safely-destructible state. That is what a move constructor (and move assignment operator) does, and it is typically a constant-time pointer swap instead of an O(n) copy.
std::move does not move anything
A very common misconception
Despite its name, std::move() does not move, copy, or modify anything by itself. It is purely a cast — it takes whatever you pass it and produces an rvalue reference to that same object, telling the compiler "treat this as if it were a temporary, it's safe to steal from it." Whether an actual move happens at all depends entirely on whether the target type has a move constructor or move assignment operator that then gets selected by overload resolution. Calling std::move on an object you continue to use afterward is a bug waiting to happen — after being moved-from, an object is left in a valid but unspecified state.
A performance-motivated example
Consider a function that builds and returns a large std::vector. Before move semantics existed, returning a container by value meant a full, expensive deep copy out of the function. With move semantics, the compiler recognizes the returned local variable is about to go out of scope and automatically treats it as movable — no copy required.

Returning and relocating a large vector without copying

CPP
#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;
}
Without std::move, the push_back(a) call would bind to the copy-inserting overload and duplicate all one million integers. With it, the move-inserting overload is selected instead, and the operation becomes a handful of pointer assignments regardless of how large the vector is.
  • 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.