C++ Move Semantics Cheat Sheet
Explains rvalue references, move constructors and assignment, std::move and std::forward, and the rule of five in modern C++.
Rvalue References
Bind to temporaries to enable move instead of copy.
void process(std::string& s) { std::cout << "lvalue ref\n"; }void process(std::string&& s) { std::cout << "rvalue ref\n"; }std::string name = "Ada";process(name); // calls lvalue overloadprocess(std::string("Bob")); // calls rvalue overload (temporary)process(std::move(name)); // calls rvalue overload (name is cast to rvalue)
Move Constructor & Move Assignment
Steal resources from a temporary instead of deep-copying.
class Buffer {public: Buffer(size_t size) : size(size), data(new int[size]) {} // Move constructor: takes ownership, leaves source in valid empty state Buffer(Buffer&& other) noexcept : size(other.size), data(other.data) { other.size = 0; other.data = nullptr; } // Move assignment Buffer& operator=(Buffer&& other) noexcept { if (this != &other) { delete[] data; data = other.data; size = other.size; other.data = nullptr; other.size = 0; } return *this; } ~Buffer() { delete[] data; }private: size_t size; int* data;};
std::move and std::forward
Cast to rvalue, and preserve value category in forwarding code.
#include <utility>std::vector<std::string> v;std::string s = "hello";v.push_back(std::move(s)); // moves s into the vector; s is now unspecified/empty// Perfect forwarding in a generic wrappertemplate <typename T>void wrapper(T&& arg) { inner(std::forward<T>(arg)); // preserves lvalue/rvalue-ness of arg}
Key Terms
Vocabulary for reasoning about value categories and moves.
- lvalue- An expression with identity/an address that persists beyond the expression, e.g. a named variable.
- rvalue- A temporary expression with no persistent identity, e.g. a literal or a function's return-by-value.
- Move Semantics- Transferring ownership of resources from a source object instead of copying them, leaving the source in a valid but unspecified state.
- noexcept on move ops- Marking move constructor/assignment noexcept lets std::vector move elements on reallocation instead of falling back to copy.
- Rule of Five- If you define a destructor, copy ctor, or copy assignment, you should typically define/delete all five: destructor, copy ctor, copy assign, move ctor, move assign.
- Copy Elision / RVO- The compiler constructs the return value directly in the caller's storage, skipping the copy/move entirely (mandatory since C++17 for prvalues).
Forwarding References & Reference Collapsing
Understand how T&& in a deduced context differs from a plain rvalue reference.
// T&& here is a FORWARDING (universal) reference because T is deduced,// not a plain rvalue reference like in a non-template functiontemplate <typename T>void wrapper(T&& arg);int x = 5;wrapper(x); // T deduced as int&, T&& collapses to int& (lvalue)wrapper(10); // T deduced as int, T&& stays int&& (rvalue)// reference collapsing rules: & + & => &, & + && => &, && + & => &, && + && => &&// NOT a forwarding reference - T is fixed by the class, so this is a plain rvalue reftemplate <typename T>struct Holder { void take(T&& arg); // T&& here does NOT deduce; ordinary rvalue reference};
Move-Only Types in Containers & Algorithms
Work with non-copyable resource handles safely inside STL containers.
std::vector<std::unique_ptr<Widget>> widgets;widgets.push_back(std::make_unique<Widget>()); // constructed in place, no copy// emplace_back forwards args to the constructor - avoids a temporary + movewidgets.emplace_back(new Widget());// erase-remove idiom with move-only elements: use std::remove_if + erase,// or since C++20, std::erase_if(widgets, pred)std::erase_if(widgets, [](const auto& w) { return w == nullptr; });// sorting a vector of move-only types works because std::sort uses// std::swap, which itself is move-based since C++11std::sort(widgets.begin(), widgets.end(), [](const auto& a, const auto& b) { return a->id < b->id; });
Moved-From State, Self-Move, and Slicing Hazards
Common correctness bugs around what 'valid but unspecified' really allows.
std::string s = "hello";std::string t = std::move(s);// s is now valid-but-unspecified: you may assign to it or destroy it,// but must NOT assume its value (e.g. do NOT assume s == "" for a custom type)s = "reset"; // fine, s is fully usable again// self-move must not corrupt the object (standard containers guarantee this// for library types, but YOUR move assignment must guard it too)Buffer b(10);b = std::move(b); // must be a safe no-op-ish result, not UB// slicing: moving through a base-class reference only moves the Base partstruct Base { std::string name; };struct Derived : Base { std::vector<int> payload; };Derived d;Base b2 = std::move(d); // SLICES: payload is silently dropped, only name moves
Guaranteed Copy Elision vs. Named RVO
Know which return-value optimizations the standard mandates versus merely permits.
struct Big { Big() { std::cout << "ctor\n"; } Big(const Big&) { std::cout << "copy\n"; } };Big makePrvalue() { return Big{}; // C++17: MANDATORY elision, zero copies/moves guaranteed}Big makeNamed() { Big local; return local; // NRVO: permitted, not guaranteed by the standard, // but every mainstream compiler performs it at -O0+}Big makeConditional(bool flag) { Big a, b; return flag ? a : b; // NRVO does NOT apply here (not a single named var) -} // this returns via move construction instead
Advanced Move Semantics Vocabulary
Precise terminology beyond the basic lvalue/rvalue split.
- Value Categories (glvalue/prvalue/xvalue)- C++11 refined lvalue/rvalue into glvalue, prvalue (pure rvalue, e.g. a literal), and xvalue (expiring value, e.g. the result of std::move).
- Forwarding Reference- T&& where T is deduced in that same call (template parameter or auto&&); binds to both lvalues and rvalues via reference collapsing.
- Perfect Forwarding- Using std::forward<T>(arg) to pass an argument onward while preserving its original value category, avoiding an unwanted copy or an incorrect move.
- Mandatory Copy Elision (C++17)- Returning a prvalue no longer requires an accessible copy/move constructor at all; the object is constructed directly in the caller's storage.
- Destructive Move- A move that also ends the source's lifetime immediately (used by std::optional/std::variant internals); distinct from the standard's 'valid but unspecified' model.
- std::exchange- Utility from <utility> that sets a variable to a new value and returns its old value in one step - simplifies writing correct move constructors.
Always mark move constructors and move assignment operators noexcept when they truly can't throw - std::vector checks this at compile time and silently falls back to copying during reallocation if the move constructor isn't noexcept, defeating the purpose.