Move Semantics
Rvalue references, move operations, forwarding, RVO, and ownership transfer.
26 questions
JuniorTheoryVery commonHow does a move constructor's signature differ from a copy constructor's?
How does a move constructor's signature differ from a copy constructor's?
A move constructor takes an rvalue reference, T(T&&), while a copy constructor takes a const lvalue reference, T(const T&). The T&& parameter is what makes the move ctor selected for rvalues, and it is usually marked noexcept.
Common mistakes
- ✗Declaring the move ctor parameter as
const T&&, which prevents stealing from the source - ✗Forgetting
noexcept, so containers fall back to copying during reallocation - ✗Assuming the move ctor is picked for lvalues without an explicit
std::move
Follow-up questions
- →Why must a move constructor not take its parameter by
constreference? - →Why do STL containers care whether the move ctor is
noexcept?
JuniorTheoryVery commonWhat is the difference between an rvalue and an rvalue reference?
What is the difference between an rvalue and an rvalue reference?
An rvalue is a value category, usually a temporary expression. An rvalue reference is a type written T&& that can bind to rvalues. A named variable of type T&& is still an lvalue expression inside its scope.
Common mistakes
- ✗Thinking every T&& expression is an rvalue
- ✗Calling another function with a named T&& without std::move or std::forward
- ✗Confusing std::move with an actual move operation
Follow-up questions
- →Why does std::move not move by itself?
- →What state is a moved-from object required to have?
JuniorTheoryVery commonHow does moving a resource differ from copying it?
How does moving a resource differ from copying it?
A copy duplicates the owned resource, leaving the source untouched and producing two independent objects. A move transfers ownership of the existing resource to the destination and leaves the source in a valid but unspecified state.
Common mistakes
- ✗Thinking a move always deep-copies the resource, just with less code
- ✗Assuming a moved-from object is destroyed and cannot be assigned a new value
- ✗Expecting a move to be cheaper than a copy for trivial types like
int
Follow-up questions
- →Why is moving an
intno faster than copying it? - →What operations are safe on a moved-from object?
JuniorTheoryVery commonWhat does std::move actually do at runtime?
What does std::move actually do at runtime?
std::move does nothing at runtime — it is purely a compile-time cast of its argument to an rvalue reference (T&&). It moves no data; the actual move happens later, and only if a move constructor or move assignment is then chosen.
Common mistakes
- ✗Believing
std::movemoves something by itself rather than just enabling a later move - ✗Calling
std::moveon an object and then expecting it to still hold its value - ✗Using
std::moveon aconstobject, where it silently falls back to a copy
Follow-up questions
- →Why does
std::moveon aconstobject give you a copy? - →What selects the move constructor over the copy constructor?
MiddleTheoryVery commonWhat is std::forward and how is it different from std::move?
What is std::forward and how is it different from std::move?
std::move unconditionally casts to an rvalue reference. std::forward<T> conditionally preserves the original value category in forwarding-reference templates.
Common mistakes
- ✗Using std::forward outside forwarding-reference templates
- ✗Using std::move on a local return value and blocking NRVO
- ✗Forwarding the same argument multiple times
Follow-up questions
- →What is a forwarding reference?
- →Why can return std::move(local) be worse than return local?
JuniorTheoryCommonWhen does the compiler move automatically without std::move?
When does the compiler move automatically without std::move?
When you return a local variable by value, the compiler treats that local as an rvalue and selects the move constructor automatically — no std::move needed. Writing return std::move(local) is harmful: it pessimizes by blocking copy elision (NRVO).
Common mistakes
- ✗Writing
return std::move(local), which blocks NRVO and pessimizes the return - ✗Believing a bare
return localmakes a copy when it actually moves or elides - ✗Adding
std::moveon areturnof a by-value parameter where it is not needed
Follow-up questions
- →What is the difference between copy elision and an automatic move on return?
- →Why is
return std::move(local)slower thanreturn local?
JuniorTheoryCommonWhy is std::unique_ptr move-only and how do you transfer ownership through APIs?
Why is std::unique_ptr move-only and how do you transfer ownership through APIs?
unique_ptr is exclusive ownership; copy would give two owners and double-delete. Copy ops deleted, move ops defined. Transfer: unique_ptr<T> by value (caller std::moves) or unique_ptr<T>&&. Borrow with T&/const T&. Return by value — move via RVO.
Common mistakes
- ✗Trying to assign
unique_ptrto another withoutstd::move— compile error - ✗Passing
unique_ptr<T>&and modifying it in the callee unexpectedly - ✗Using
unique_ptr<T>for borrowing — confuses ownership semantics
Follow-up questions
- →How does
std::move(uniquePtr)differ fromuniquePtr.release()? - →Why is
unique_ptrzero-overhead compared to a raw pointer?
MiddleTheoryCommonWhat is the state of an object after it has been moved from?
What is the state of an object after it has been moved from?
After move, the object is valid but unspecified — you may destroy it, assign to it, or call operations with no preconditions (clear(), empty()), but cannot rely on its value. Standard types are usually empty by convention, not guarantee.
Common mistakes
- ✗Reading a moved-from object's value and finding it surprisingly non-empty
- ✗Calling functions with preconditions (e.g.
front()on possibly-empty vector) on moved-from objects - ✗Assuming your custom move-ctor leaves the source in a usable state without explicitly setting it
Follow-up questions
- →What invariants should your custom move ctor preserve?
- →Why is
std::unique_ptrafter move guaranteed to benullptr?
MiddleTheoryCommonHow should a derived class call its base class's move operations?
How should a derived class call its base class's move operations?
Write Derived(Derived&& o) noexcept : Base(std::move(o)), m_(std::move(o.m_)) {}. std::move(o) is required — o is a named lvalue in the body, so without it the base copy ctor is picked. In move-assign: Base::operator=(std::move(o)); then members.
Common mistakes
- ✗Forgetting
std::move(other)and silently calling base's copy ctor - ✗Moving members in the wrong order (initialiser list runs in declaration order)
- ✗Throwing in the move list and leaving the object partially moved
Follow-up questions
- →Why does the move ctor parameter
othercount as an lvalue inside the body? - →What if the base has only a copy ctor and you want to keep moves cheap?
MiddleTheoryCommonWhat is copy elision (RVO/NRVO)? How many constructor/destructor calls happen?
What is copy elision (RVO/NRVO)? How many constructor/destructor calls happen?
Copy elision constructs the return value directly in caller storage, skipping copy/move ctor. RVO applies to temporaries (mandatory since C++17 for prvalues); NRVO to named locals (optional). Under mandatory RVO, T x = f(); calls one ctor.
Common mistakes
- ✗Returning different named variables conditionally and expecting NRVO — NRVO requires a single candidate; conditionals usually prevent it
- ✗Adding
std::moveon a return statement for a local variable — prevents NRVO (the compiler cannot elide a move, only a copy/construct) - ✗Thinking copy elision is always applied — in debug builds
-O0, NRVO may be disabled
Follow-up questions
- →What is the difference between copy elision and implicit move on return (C++11)?
- →How many constructor calls occur in
T a = T(T(T()))with and without C++17 RVO?
MiddleTheoryCommonWhat happens if you = delete the move constructor?
What happens if you = delete the move constructor?
A deleted move ctor still participates in overload resolution (declared, just deleted) and is picked as best match for rvalues — which becomes a hard compile error. T x = std::move(y); fails to compile rather than silently falling back to copy.
Common mistakes
- ✗
=delete'ing move to forbid moves but unintentionally forbidding even copies - ✗Forgetting that not declaring move and disabling copy makes the type immovable
- ✗Mixing
= deleteand= defaultcarelessly across the five special members
Follow-up questions
- →What is the difference between not declaring move and
= deletemove? - →How does
= deleteinteract with implicit conversions?
MiddleTheoryCommonWhen does the compiler implicitly generate a move constructor and when does it suppress it?
When does the compiler implicitly generate a move constructor and when does it suppress it?
Implicitly generated only if none of (dtor, copy ctor/assign, move ctor/assign) is user-declared and all members/bases are movable. Declaring any of those suppresses the implicit move — the class falls back to copy.
Common mistakes
- ✗Adding
~T() = default;thinking it's harmless — it suppresses the implicit move - ✗Defining copy ctor manually and being puzzled why move calls fall back to copy
- ✗Defining only move (= default) and getting deleted copy automatically
Follow-up questions
- →Why is the special-member generation rule so restrictive?
- →What is the difference between defaulted and trivial special member?
MiddleTheoryCommonWhat happens when you std::move a const object?
What happens when you std::move a const object?
std::move(x) returns static_cast<remove_reference_t<T>&&>(x) — for const T it's const T&&. Move ctor takes T&& (non-const), so overload resolution falls through to copy ctor (const T&). Silent copy, no error.
Common mistakes
- ✗Marking parameters
const T&&thinking that's an rvalue ref — it's a const-rvalue ref, almost useless - ✗Returning a
constlocal — disables move/RVO, forces copy - ✗Forgetting that this fallback to copy is silent — perf regression hiding
Follow-up questions
- →When would
const T&&actually be useful as a parameter? - →Why does returning
const Tdefeat both RVO and move?
MiddleDesignCommonYou are writing a setter that stores its argument into a member, and you want it to work efficiently whether the caller passes an lvalue or an rvalue without writing two overloads. Explain the "pass by value, then move" idiom this leads to — how the parameter is constructed in each case, what the body does — and the trade-off that decides when it is the right choice.
You are writing a setter that stores its argument into a member, and you want it to work efficiently whether the caller passes an lvalue or an rvalue without writing two overloads. Explain the "pass by value, then move" idiom this leads to — how the parameter is constructed in each case, what the body does — and the trade-off that decides when it is the right choice.
Instead of overloading set(const T&) and set(T&&), write set(T x) { m_ = std::move(x); }. The parameter is built once: copy from lvalue, move from rvalue. The body always moves into the member. Cost: one extra cheap move; benefit: half the overloads.
Common mistakes
- ✗Using by-value for expensive-to-move types — every call pays an extra move
- ✗Forgetting the
std::moveinside the body — copies into the member instead of moving - ✗Mixing this idiom with templates where the parameter type is unknown
Follow-up questions
- →When does the by-value version produce identical machine code to the two-overload version?
- →How does perfect forwarding compare for templated setters?
MiddleTheoryCommonShould a move assignment operator handle self-assignment and how?
Should a move assignment operator handle self-assignment and how?
Self-move (x = std::move(x)) is rare but possible. Moved-from objects stay 'valid but unspecified', so no-op is fine. Copy-and-swap is automatically safe; manual transfer needs if (this != &other) or release-before-acquire ordering.
Common mistakes
- ✗Calling
deleteon the source's pointer before moving — destroys the source you're about to move from - ✗Skipping self-check and getting a destroyed object on
x = std::move(x) - ✗Asserting
this != &otherinstead of handling it gracefully
Follow-up questions
- →Why does copy-and-swap automatically handle self-assignment?
- →Should you write self-checks in copy-assign too?
MiddleTheoryCommonWhat happens to copy operations if you declare a move constructor?
What happens to copy operations if you declare a move constructor?
Declaring a move constructor or move assignment suppresses the implicit copy operations — they become deleted unless you explicitly default or define them. The rule is symmetric: declaring copy ops suppresses the implicit move.
Common mistakes
- ✗Adding a move constructor and expecting copying to keep working
- ✗Forgetting noexcept on move operations used by containers
- ✗Writing resource-owning classes without defining all required special members
Follow-up questions
- →What is the Rule of Five?
- →Why is Rule of Zero preferable?
MiddleTheoryCommonWhich types do not benefit from move and why?
Which types do not benefit from move and why?
Types whose representation is entirely value-based with no owned heap resource: int, double, small POD structs, std::array<T, N> of trivial T. For these, move is identical to copy because there's no pointer to steal.
Common mistakes
- ✗Manually writing move for a struct of three ints — useless boilerplate
- ✗Expecting
std::moveon anintto do anything magical — it just casts to rvalue - ✗Wrapping a value type in
unique_ptrto enable move when copy is already cheap
Follow-up questions
- →What is a 'trivially copyable' type and how does it relate to move?
- →Why is move on
std::array<int, 100>actually a copy?
MiddlePerformanceOccasionalCan std::move make a struct with a large built-in array cheap to move?
Can std::move make a struct with a large built-in array cheap to move?
No. A built-in array is part of the object's storage, so the generated move element-wise moves/copies every entry. Move is cheap only when the object hands off an external resource — like a heap buffer pointer transferred without touching the data.
Common mistakes
- ✗Assuming std::move always means O(1)
- ✗Using huge inline arrays where std::vector would express transferable ownership
- ✗Forgetting that std::move is only a cast
Follow-up questions
- →How would std::vector<int> behave differently?
- →What does a defaulted move constructor do for array members?
MiddleDesignOccasionalWhen you design a class that owns resources, the Rule of Zero and the Rule of Five guide whether you should write the special member functions (destructor, copy/move constructor and assignment) yourself. Explain what each rule prescribes and what about a class's members decides which rule applies.
When you design a class that owns resources, the Rule of Zero and the Rule of Five guide whether you should write the special member functions (destructor, copy/move constructor and assignment) yourself. Explain what each rule prescribes and what about a class's members decides which rule applies.
Rule of Zero: if all members manage their resources (containers, smart pointers, RAII wrappers), define none of the specials. Rule of Five: if you must define one of (dtor, copy ctor/assign, move ctor/assign), consider all five — manual ownership means Rule of Five.
Common mistakes
- ✗Defining only the destructor, leaving compiler-generated copy that double-deletes
- ✗Defaulting all five but with a non-trivial member — compiler is fine; the bug is elsewhere
- ✗Wrapping a
unique_ptrand then writing all five — unnecessary
Follow-up questions
- →What does '= default' on a special member mean?
- →When does a class become non-copyable automatically?
MiddleTheoryOccasionalWhich STL operations leverage move, and which still copy?
Which STL operations leverage move, and which still copy?
Inserts (push_back, emplace_back, insert) move from rvalues. vector realloc moves only if noexcept. Algorithms std::move, std::rotate, std::swap move; std::copy, std::copy_n, std::transform copy. C++20 range adaptors use views::move.
Common mistakes
- ✗Calling
std::copy(src.begin(), src.end(), dst)when you wanted to move — copies - ✗Not marking move noexcept and being shocked vector still copies
- ✗Confusing the algorithm
std::move(first, last, dest)with the caststd::move(x)
Follow-up questions
- →How does
std::move_iteratoradapt a range for moving algorithms? - →Why does
std::list::splicenot need move?
SeniorTheoryOccasionalHow does std::move_if_noexcept decide between move and copy?
How does std::move_if_noexcept decide between move and copy?
Returns an rvalue reference if move ctor is noexcept or the type has no copy ctor; otherwise a const lvalue reference, so the next construction copies. Containers use it on reallocation for the strong exception guarantee.
Common mistakes
- ✗Wrapping
std::move(x)instead ofstd::move_if_noexcept(x)in a container reallocation — UB on throw - ✗Believing
move_if_noexceptalways moves — for non-noexcept move + copyable types it copies - ✗Forgetting that move-only + non-noexcept move still moves (no copy fallback exists)
Follow-up questions
- →What is
std::move_iteratorand how does it interact with this? - →Why doesn't
vector::push_backusemoveunconditionally?
SeniorTheoryOccasionalWhy must your move constructor be noexcept for std::vector to use it during reallocation?
Why must your move constructor be noexcept for std::vector to use it during reallocation?
vector::push_back gives the strong exception guarantee — if a move throws mid-reallocation, vector cannot roll back. So it uses std::move_if_noexcept: noexcept move → move, else → copy. A non-noexcept move silently becomes a copy.
Common mistakes
- ✗Forgetting
noexcepton a move ctor that doesn't throw — perf regression - ✗Marking move
noexceptwhen it actually can throw — terminate at runtime - ✗Not testing move/copy paths separately to catch the regression
Follow-up questions
- →How is
std::move_if_noexceptimplemented? - →Why is the strong guarantee important for vector but not for list?
SeniorPerformanceOccasionalWhat is std::move pessimization and when does adding move make code slower?
What is std::move pessimization and when does adding move make code slower?
return std::move(local) blocks NRVO/RVO copy elision because std::move(local) is no longer the local object itself, but an rvalue expression referring to it. Without elision you pay a real move call — cheaper than copy, but not zero.
Common mistakes
- ✗Adding
std::move'just in case' to return statements — disables RVO - ✗Wrapping a temporary
T(args)instd::move(T(args))— temporary is already rvalue - ✗Returning
std::move(member)from a getter and being puzzled why callers see junk
Follow-up questions
- →What is the difference between RVO and NRVO?
- →Since C++17, when is copy elision mandatory?
SeniorTheoryOccasionalWhat are ref-qualified member functions (& / &&) and what do they enable?
What are ref-qualified member functions (& / &&) and what do they enable?
Methods can be qualified by this's value category: T m() & runs on lvalues, T m() && on rvalues. Useful for different bodies — e.g. Buffer::data() && can move out the storage since the object is dying, while Buffer::data() const & returns a const view.
Common mistakes
- ✗Forgetting that overloading on
&and&&requires both — otherwise hides one - ✗Returning a moved-out reference from
&&and using it after the temporary dies - ✗Mixing ref-qualifiers with cv-qualifiers in surprising orders
Follow-up questions
- →When does C++23 deducing-this replace ref-qualified overloads?
- →How does ref-qualifier interact with overload resolution and
this?
SeniorPerformanceOccasionalHow does small-string optimisation interact with std::string's move?
How does small-string optimisation interact with std::string's move?
If the source fits in the inline buffer (SSO), move copies inline bytes — no heap pointer to swap. 'Move is free' is wrong for short strings; SSO-move is essentially a copy. Long strings steal the heap pointer (O(1)). SSO threshold varies (~15–23 bytes).
Common mistakes
- ✗Assuming
std::move(short_string)is free — for SSO sizes it's a copy - ✗Designing benchmarks with consistently short strings and concluding move is slow
- ✗Forgetting that SSO threshold differs between libstdc++, libc++, MSVC STL
Follow-up questions
- →How does
std::string's SSO buffer typically lay out? - →Why does
string_viewnot have SSO concerns?
SeniorTheoryOccasionalWhen does std::vector use move instead of copy on reallocation?
When does std::vector use move instead of copy on reallocation?
std::vector uses move on reallocation only if move ctor is noexcept (std::is_nothrow_move_constructible). Otherwise it falls back to copy for the strong exception guarantee: a throwing move mid-buffer cannot be undone, copy leaves the original intact.
Common mistakes
- ✗Defining a move constructor that can throw — silently prevents vector from using it on reallocation
- ✗Not using
noexcepton move operations in custom containers or wrappers — propagates the performance penalty - ✗Thinking
std::moveinside a container is the only place this matters —std::deque,std::unordered_mapbucket rehash follow the same rule
Follow-up questions
- →How does
std::vector::reserveavoid reallocation and why should you call it when the size is known in advance? - →What is
std::move_if_noexceptand where is it used in the standard library?