Move Semantics

By the end of this lesson you'll know exactly why std::move makes code faster, how to write a move constructor and move assignment that steal resources instead of copying them, and how the standard library uses moves under the hood — so your own types stop paying for needless copies.

Part of the free C++ course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

What You'll Learn

💡 Real-World Analogy

Imagine moving house. A copy is photocopying every book, re-buying every piece of furniture, and rebuilding it all at the new address — slow and wasteful. A move is loading the moving truck and driving it over: the same stuff arrives at the new place, and the old house is now empty. Nothing was duplicated; ownership of your belongings simply transferred . That is exactly what a move constructor does — it hands the internal buffer to the new object and leaves the old one empty but still safe to clean up.

1. lvalues, rvalues, and T&&

Every expression in C++ is either an lvalue or an rvalue . An lvalue has a name and a lasting address — like a variable x ; you can take its address with &x . An rvalue is a temporary with no lasting identity — like the literal 42 , or the result of x + 1 ; it vanishes at the end of the line. This matters because you can safely steal from an rvalue: nothing else will ever look at it again.

A plain reference T& binds to lvalues. The new tool is the rvalue reference , written T&& (two ampersands), which binds to rvalues — to temporaries. That double-ampersand parameter is how a function says "I will only run for throwaway values, so I'm allowed to gut them."

2. std::move and the Move Constructor

std::move sounds like it moves data, but it does nothing at runtime . It is just a cast that turns an lvalue into an rvalue reference, so the compiler picks the move constructor ( T(T&&) ) instead of the copy constructor ( T(const T&) ). The move constructor is where the real work happens: it steals the source's internal buffer — usually a single pointer swap — instead of duplicating millions of elements. The source is left empty but valid.

Read this worked example and run it. The class prints whether a copy or a move ran, so you can see exactly which constructor the compiler chose.

Your turn. The class below has a working copy constructor; the move constructor is missing two pieces. Fill in the ___ blanks so it steals the buffer and promises not to throw.

Now practise calling std::move yourself. Moving a std::string hands its character buffer to the destination and leaves the original empty — no characters are copied.

🔎 Deep Dive: why moving is cheap

A std::string or std::vector is a small handle (a pointer to a heap buffer, plus a size and capacity). Copying allocates a brand-new buffer and copies every byte — O(n). Moving copies just the pointer, size, and capacity, then zeroes out the source so it doesn't free the buffer you stole — O(1), no matter how big the data is.

That is the whole point: a move transfers ownership of the existing resource instead of building a second copy of it.

3. Move Assignment and the Rule of Five

A move constructor builds a new object from a temporary. Move assignment ( operator=(T&&) ) instead replaces an existing object: it must first free whatever it already holds, then steal the source's resource. The Rule of Five says that once you manually manage a resource and write any one of these five special members, you should write all five so they agree on ownership:

The five special members

Destructor, copy constructor, copy assignment, move constructor, move assignment. Notice the move assignment guards against self-move with if (this != &o) before it deletes anything — without that check, assigning an object to itself would free its own buffer first.

In real code you'd usually let members like std::vector manage the memory so the compiler generates all five correctly for you. Write them by hand only when you own a raw resource.

4. How std::vector Uses Moves

When a std::vector runs out of capacity, it allocates a bigger buffer and transfers the existing elements into it. If your type's move constructor is marked noexcept , the vector moves each element (cheap). If it isn't, the vector copies them instead — it needs the strong exception guarantee, and a move that might throw could leave it in a broken state. That one keyword is the difference between fast and slow growth.

Common Errors (and the fix)

📋 Quick Reference

Concept

Syntax

Meaning

lvalue reference

T& r = x;

Binds to a named object

rvalue reference

T&& r = T();

Binds to a temporary

Cast to rvalue

std::move(x)

Enables a move (no runtime cost)

Move constructor

T(T&&) noexcept

Build by stealing resources

Move assignment

T& operator=(T&&)

Replace by stealing resources

Rule of Five

~T, copy x2, move x2

Define all five together

Frequently Asked Questions

Mini-Challenge: a move-aware Document

No blanks this time — just a brief and an outline. Build a class with both a copy and a move constructor, then prove which one runs by copying once and moving once. Check your output against the comments.

Pro Tips

🎉 Lesson Complete

Practice quiz

What is the difference between an lvalue and an rvalue?

  • An lvalue is always const; an rvalue is mutable
  • An lvalue is on the heap; an rvalue is on the stack
  • An lvalue has a name and a stable address; an rvalue is a temporary with no lasting identity
  • There is no difference

Answer: An lvalue has a name and a stable address; an rvalue is a temporary with no lasting identity. An lvalue (like a variable x) has a name and address; an rvalue (like 42 or x+1) is a throwaway temporary you can steal from.

What does std::move actually do at run time?

  • Nothing at run time — it is just a cast to an rvalue reference
  • It copies the data to a new location
  • It frees the source object
  • It allocates new memory

Answer: Nothing at run time — it is just a cast to an rvalue reference. std::move moves nothing; it casts an lvalue to an rvalue reference so the compiler picks the move constructor/assignment.

Which reference type binds to a temporary (rvalue)?

  • int&
  • const int*
  • int*&
  • int&&

Answer: int&&. int&& is an rvalue reference and binds to temporaries; int& binds to named lvalues.

After string b = std::move(a); for a std::string a, what is a's state?

  • a still holds its original characters
  • a is valid but unspecified — for string typically empty
  • a is destroyed and unusable
  • a now points to b

Answer: a is valid but unspecified — for string typically empty. A moved-from object is valid but unspecified; std::string is typically left empty. Don't rely on its value.

Why does moving a std::vector cost O(1) while copying costs O(n)?

  • Moving copies just the pointer, size, and capacity, then zeroes the source
  • Moving compresses the data
  • Copying skips the elements
  • Moving uses a faster memcpy of every element

Answer: Moving copies just the pointer, size, and capacity, then zeroes the source. A move steals the heap buffer by copying the small handle (pointer/size/capacity); copying duplicates every element.

Why must a move constructor be marked noexcept?

  • It is required for the class to compile
  • It makes the move faster by skipping checks
  • So std::vector moves (rather than copies) elements when it reallocates
  • It prevents the destructor from running

Answer: So std::vector moves (rather than copies) elements when it reallocates. std::vector only uses your move ctor on reallocation if it promises not to throw; without noexcept it copies for the strong guarantee.

What are the five special members in the Rule of Five?

  • Constructor, destructor, copy ctor, copy assignment, swap
  • Destructor, copy ctor, copy assignment, move ctor, move assignment
  • Constructor, destructor, operator+, operator==, operator<<
  • Two constructors and three destructors

Answer: Destructor, copy ctor, copy assignment, move ctor, move assignment. The Rule of Five: destructor, copy constructor, copy assignment, move constructor, and move assignment all go together.

Why does move assignment guard with if (this != &o) before deleting?

  • To skip the work when objects are equal in value
  • To make the operation noexcept
  • It is purely stylistic
  • To avoid freeing its own buffer on a self-move like x = std::move(x);

Answer: To avoid freeing its own buffer on a self-move like x = std::move(x);. Without the self-move guard, deleting before stealing would free the very buffer it is about to take.

What happens if you call std::move on a const object, like move(c) where c is const?

  • It moves as usual
  • It produces a const T&&, which binds to the copy constructor — you silently get a copy
  • It is a compile error
  • It throws at run time

Answer: It produces a const T&&, which binds to the copy constructor — you silently get a copy. move(const T) yields const T&&, which can't bind to a non-const move ctor, so the copy constructor runs instead.

For a local variable, what should you write to return it most efficiently?

  • return std::move(local);
  • return &local;
  • return local; — letting the compiler elide (RVO)
  • return *local;

Answer: return local; — letting the compiler elide (RVO). return local; lets the compiler apply copy elision/RVO; return std::move(local); actually disables that optimisation.

Continue this course