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.

Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.

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.

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.

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:

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.

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.

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.