Skip to content
C++

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 this

Move-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>);
Edit on GitHub