5.2.9 · D1C++ Programming

Foundations — Move semantics — rvalue references (&&), std - move, std - forward

3,544 words16 min readBack to topic

Before you can read the parent note, you must own every symbol it throws at you. We build them one at a time — each one earns its place before the next appears.


0. The running example: a toy class Str

The parent note's derivations all use a tiny hand-written string class called Str. Every signature below (Str(const Str&), Str(Str&&), operator=) refers to this class, so we define it here before using its name anywhere else.


1. What "an object owning heap memory" even means

Figure — Move semantics — rvalue references (&&), std - move, std - forward

Look at the figure: the box on the left (str) sits on the stack. It holds an address (the pointer) and a length (how many characters are in use). The long buffer on the right sits on the heap. This split is the reason move semantics can be cheap: to hand off the whole thing, you only need to copy those few little numbers on the left, not the long buffer on the right.


2. The symbol * — a pointer, and what "owning" it means


3. The symbol & — "address of" AND "reference"

The parent note uses & in two totally different jobs. You must separate them.


4. const T& — a read-only alias that can outlive a temporary


5. lvalue vs rvalue — the single most important split

This is the concept the entire topic pivots on. Ignore the scary names; look at the picture.

Figure — Move semantics — rvalue references (&&), std - move, std - forward
Definition The finer value categories (glvalue, prvalue, xvalue)

C++ actually splits expressions into five categories, built from two independent yes/no questions: (i) does it have identity (a stable address you could name)? (ii) can it be moved from (plundered)? Two questions with two answers each give a little 2×2 grid, and the category names are just labels on that grid:

  • glvalue ("generalized lvalue") = answers yes to identity. Splits into:
    • lvalue = identity yes, movable no (e.g. x, arr[i]). The named things.
    • xvalue ("eXpiring value") = identity yes, movable yes (e.g. the result of std::move(x), or a function returning Str&&). It has a home but you've flagged it dying.
  • rvalue = answers yes to movable. Splits into:
    • prvalue ("pure rvalue") = identity no, movable yes — a fresh temporary (e.g. 42, a + b, makeStr()).
    • xvalue again. Why xvalue appears under both glvalue and rvalue: the two questions are independent, so "identity yes" and "movable yes" can both be true at once — and the xvalue is exactly that corner of the grid. Being "identity yes" makes it a glvalue; being "movable yes" makes it an rvalue. It is not a contradiction; it is simply the one category that answers yes to both questions. That is the whole point of std::move(x): it takes a named thing (identity yes) and adds the movable flag, producing the yes/yes corner. Why this matters for std::forward: a forwarding reference must be able to reproduce either a plain lvalue (identity yes, movable no) or an xvalue (identity yes, movable yes) depending on what the caller sent; that is precisely what std::forward does (section 8). For the parent note's day-to-day reasoning, "lvalue vs rvalue" is enough — this box is the precise version underneath it.

6. T&& — the rvalue reference (and overload resolution)

The figure is the binding table as a picture: three doors (T&, const T&, T&&), two visitors (an lvalue and an rvalue). Notice T&& only opens for the temporary — that exclusivity is what lets overload resolution route temporaries into the fast move path. This same table drives Rule of Five.


7. std::move, the move constructor, and move assignment


8. Templates, type deduction & reference collapsing — where std::forward lives


9. noexcept — the promise that unlocks fast moves


10. ~ClassName() and the double-delete danger


Prerequisite map

stack vs heap

pointer star

RAII ownership

ampersand address-of

lvalue vs rvalue

reference T and

rvalue reference T andand

overload resolution

std move and move ctor

templates and deduction

reference collapsing

std forward

Move semantics

noexcept

destructor and double delete


Equipment checklist

What are the two data members of the toy class Str, and what does each hold?
char* data — a pointer holding the address of the heap character buffer; size_t len — an unsigned integer holding the count of characters.
Where does the actual data of a Str/std::string live, and what does the stack object hold?
The characters live on the heap; the small stack object holds a pointer (address) and a length (a real std::string adds a capacity and often a small-string buffer).
What does RAII stand for and mean?
Resource Acquisition Is Initialization — an object grabs a resource in its constructor and releases it in its destructor, tying the resource's lifetime to the object's.
What is the difference between &x and int&?
&x is the address-of operator in an expression; int& is a reference type (an alias). Different jobs, same symbol.
Why is binding a temporary to const T& safe?
Because binding a temporary to a const lvalue reference extends the temporary's lifetime to match the reference — it won't vanish under you.
Which reference type binds only to rvalues?
T&& (rvalue reference).
Name the five value categories and the two questions defining them.
glvalue, prvalue, xvalue (and their parents lvalue, rvalue); defined by "has identity?" and "may be moved from?" — an xvalue answers yes to both, so it is both a glvalue and an rvalue.
What is overload resolution?
The compiler's step of choosing which same-named function to call based on the argument's value category (rvalue prefers T&& move; lvalue reaches const T& copy).
How does a move constructor differ from a copy constructor?
The copy ctor deep-duplicates the buffer; the move ctor steals the source's pointer and blanks the source. Move also has a partner: the move assignment operator for existing = temporary;.
Does std::move move anything at runtime?
No — it is static_cast<T&&>(x), a zero-cost relabel producing an xvalue so overload resolution picks the move path.
State all four reference-collapsing rules.
T& &T&, T& &&T&, T&& &T&, T&& &&T&& (only && && stays &&).
In f(x) vs f(makeInt()) for template<class T> void f(T&& a), what is T?
For the lvalue x, T = int& (collapses a to int&); for the prvalue, T = int (a is int&&).
Why does a noexcept move constructor unlock std::vector's fast path?
Because during reallocation a throwing move could leave the vector corrupt mid-relocation; std::vector only moves elements if the move is noexcept, otherwise it safely copies.