Move Semantics Quick Reference
C++ move semantics cheatsheet — rvalue references, std::move, std::forward, Rule of Five, move-only types, and perfect forwarding patterns.
Value Categories
cpp
lvalue — has name, addressable: int x = 5; x is lvalue
prvalue — pure temporary: 5, x+1, f() are prvalues
xvalue — expiring value: std::move(x), return by value
lvalue reference T& — binds lvalues
rvalue reference T&& — binds rvalues (prvalue or xvalue)std::move and std::forward
cpp
// std::move — cast to rvalue (does NOT move anything itself)
template<typename T>
constexpr std::remove_reference_t<T>&&
move(T&& t) noexcept { return static_cast<...>(t); }
// Usage
std::string a = "hello";
std::string b = std::move(a); // b="hello", a valid but unspecified
// std::forward — preserves value category (use in templates)
template<typename T>
void relay(T&& arg) {
sink(std::forward<T>(arg)); // lvalue if T=X&, rvalue if T=X
}Rule of Five
cpp
class Resource {
int* data_;
size_t n_;
public:
// 1. Destructor
~Resource() { delete[] data_; }
// 2. Copy constructor
Resource(const Resource& o)
: data_{new int[o.n_]}, n_{o.n_} {
std::copy_n(o.data_, n_, data_);
}
// 3. Copy assignment
Resource& operator=(const Resource& o) {
Resource tmp{o}; // copy-and-swap
swap(*this, tmp);
return *this;
}
// 4. Move constructor — noexcept!
Resource(Resource&& o) noexcept
: data_{std::exchange(o.data_, nullptr)}
, n_{std::exchange(o.n_, 0)} {}
// 5. Move assignment — noexcept!
Resource& operator=(Resource&& o) noexcept {
if (this != &o) {
delete[] data_;
data_ = std::exchange(o.data_, nullptr);
n_ = std::exchange(o.n_, 0);
}
return *this;
}
friend void swap(Resource& a, Resource& b) noexcept {
std::swap(a.data_, b.data_);
std::swap(a.n_, b.n_);
}
};Rule of Zero (preferred)
cpp
// Compose RAII members — no manual Rule of Five needed
class Better {
std::vector<int> data_; // handles copy/move automatically
std::unique_ptr<Foo> foo_; // move-only (non-copyable)
std::shared_ptr<Bar> bar_; // reference-counted copy
std::string name_;
// Compiler-generated: copy ctor, copy assign, move ctor, move assign, dtor
// All correct by composition
};Perfect Forwarding Pattern
cpp
// Factory with perfect forwarding
template<typename T, typename... Args>
std::unique_ptr<T> make(Args&&... args) {
return std::make_unique<T>(std::forward<Args>(args)...);
}
// Forwarding wrapper
template<typename F, typename... Args>
auto call_logged(F&& f, Args&&... args) {
log("calling");
return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}When to use std::move
cpp
// 1. Return local by value — let RVO/NRVO work, don't move
Widget make() {
Widget w;
return w; // NRVO kicks in — don't add std::move here
}
// 2. Move into a function parameter (sink)
void store(std::string s); // sink: takes by value
store(std::move(my_string)); // avoids copy
// 3. Move into a member
class Holder {
std::string data_;
public:
Holder(std::string s) : data_{std::move(s)} {}
};
// 4. Move from last use of a local
auto result = heavy_compute();
cache_.push_back(std::move(result)); // result not used after thisMove-Only Types
cpp
// unique_ptr, thread, fstream, promise are move-only
std::unique_ptr<int> p = std::make_unique<int>(42);
// auto p2 = p; // error: deleted copy constructor
auto p2 = std::move(p); // OK: p is now null
// Store move-only in containers
std::vector<std::unique_ptr<Base>> v;
v.push_back(std::make_unique<Derived>());
v.emplace_back(std::make_unique<Derived2>());
// Move-only lambda capture
auto resource = std::make_unique<HeavyObj>();
auto task = [r = std::move(resource)]() {
r->do_work();
};Common Mistakes
cpp
// 1. Moving and then using
std::string s = "hello";
auto t = std::move(s);
std::println("{}", s); // UB: s is valid but unspecified
// 2. Moving from const — silently copies
const std::string cs = "hi";
auto u = std::move(cs); // copies! move from const isn't move
// 3. Named rvalue ref is an lvalue
void f(Widget&& w) {
g(w); // passes as lvalue!
g(std::move(w)); // correct: explicitly cast to rvalue
}
// 4. std::move in return kills NRVO
Widget make() {
Widget w;
return std::move(w); // prevents NRVO — worse performance
}Reference Collapsing Rules
cpp
T& & → T&
T& && → T&
T&& & → T&
T&& && → T&&cpp
// T deduced as int& when called with lvalue
template<typename T> void f(T&& x);
int n = 0;
f(n); // T = int&, T&& = int& && = int& (forwarding ref)
f(5); // T = int, T&& = int&& (rvalue ref)noexcept on Moves
cpp
// std::vector moves elements during realloc ONLY if move is noexcept
class MyType {
// MUST be noexcept for vector reallocation optimization:
MyType(MyType&&) noexcept;
MyType& operator=(MyType&&) noexcept;
};
// Check if a move is noexcept
static_assert(std::is_nothrow_move_constructible_v<std::string>);