Skip to content
Move Semantics and Rvalue References
step 1/7

Reading — step 1 of 7

Learn

~3 min readModern C++ Patterns

Move semantics (C++11+) lets you transfer ownership of resources without copying — turning expensive operations into cheap ones. Built on rvalue references (T&&).

The motivating problem

std::vector<int> bigVec(1'000'000);
std::vector<int> v = bigVec;        // ❌ deep copy of 1M ints
std::vector<int> v2 = std::move(bigVec);  // ✓ move — cheap pointer transfer

Returning by value, passing arguments — many operations would be expensive copies if every assignment had to duplicate. Move semantics says: if the source is about to die anyway, just steal its insides.

lvalues vs rvalues

  • lvalue — has an identity, you can take its address. Variables, references, anything you can put on the left of =.
  • rvalue — temporary, no name. Function return values, literals, expression results.
int x = 5;          // x is an lvalue; 5 is an rvalue
int y = x + 1;      // x is lvalue; (x + 1) is rvalue (a temporary)
int& lref = x;      // lvalue reference — binds to lvalues
int&& rref = 5;      // rvalue reference — binds to rvalues
int&& rref2 = std::move(x);   // turns lvalue x INTO an rvalue

Move constructors and assignment

A move constructor takes an rvalue reference of its own type:

class Buffer {
public:
    Buffer(Buffer&& other) noexcept
        : data_(other.data_), size_(other.size_)
    {
        other.data_ = nullptr;   // STEAL — leave other in valid empty state
        other.size_ = 0;
    }
private:
    int* data_;
    size_t size_;
};

The key: COPY just the pointer/handle, then NULL OUT the source's pointer so its destructor doesn't free what we just stole.

std::move — "convert to rvalue"

std::move(x) doesn't actually move — it CASTS x to an rvalue reference, signaling "feel free to steal from me."

std::string a = "hello";
std::string b = std::move(a);   // calls move ctor; a is now empty
// using a here is valid but the value is unspecified — typically empty

After move, the source is in a valid but unspecified state. You can assign to it or destroy it; you shouldn't read its value.

When does move happen automatically?

The compiler picks move over copy in two main places:

  1. Returning a local by value (Return Value Optimization or implicit move):
std::vector<int> makeVec() {
    std::vector<int> v = {1, 2, 3};
    return v;        // moves (or RVO elides the copy entirely)
}
  1. Function argument is an rvalue:
void take(std::vector<int>);
take(std::vector<int>{1, 2, 3});   // argument is a temporary → moves in

Pass-by-value with move — the modern idiom

class User {
public:
    User(std::string name) : name_(std::move(name)) {}   // move from the parameter
private:
    std::string name_;
};

User u("Alice");           // string created from literal, moved into name_
std::string s = "Bob";
User u2(s);                 // copy s into parameter, then move parameter into name_
User u3(std::move(s));      // move s into parameter, then move parameter into name_

One function signature handles all cases efficiently. Modern C++ best practice.

Common mistakes

  • Using a moved-from object — valid but unspecified state. Don't read; reassign first.
  • Forgetting noexcept on move ops — STL containers may fall back to copy.
  • std::move on a const value — silently doesn't move (can't modify const). Move ctors take non-const rvalue ref.
  • Returning std::move(local) — usually inhibits RVO, slightly worse. Just return local; lets the compiler pick.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…