LearnFrenzy
Sign in Take a free test

Memory, Smart Pointers and Move · Lesson 14 of 33

Move Semantics and the Rule of Five

6 min read Updated Nov 7, 2026 Saurabh Samir

Copying a vector of a million elements copies a million elements. But if the original is about to be thrown away anyway, why not just take its memory? That's move semantics (C++11): transferring resources instead of copying them. It's why returning a big vector from a function is cheap, and it's a standard interview topic for experienced C++ roles.

In this lesson

  • lvalues, rvalues and rvalue references
  • Copy vs move, and std::move
  • Writing a move constructor and move assignment
  • The rules of zero, three and five

Copy vs move

#include <iostream>
#include <string>
#include <utility>
#include <vector>

int main() {
    std::vector<int> big(1'000'000, 7);

    std::vector<int> copy = big;               // copies a million ints
    std::vector<int> moved = std::move(big);   // steals big's buffer: no element is copied

    std::cout << "copy: " << copy.size() << ", moved: " << moved.size()
              << ", big after move: " << big.size() << '\n';   // valid but unspecified – empty in practice

    std::string name = "Asha Verma, Computer Science";
    std::vector<std::string> list;
    list.push_back(name);                      // copy: name still needed
    list.push_back(std::move(name));           // move: we're done with name
    std::cout << list[1] << " | name is now \"" << name << "\"\n";
    return 0;
}

Output

copy: 1000000, moved: 1000000, big after move: 0
Asha Verma, Computer Science | name is now ""

What std::move(big) does

  1. std::movedoesn't move anything – it just casts big to an rvalue reference ("you may steal from me")
  2. The move constructor of movedtakes big's pointer, size and capacity
  3. big is left emptyin a valid state: you may assign to it or destroy it, but don't rely on its value

lvalues and rvalues

  • An lvalue has a name and an address: name, v[0]. It may be used again, so it's copied.
  • An rvalue is a temporary about to disappear: a + b, std::string("x"), a function's return value. It can safely be moved from.
  • T&& is an rvalue reference: it binds to temporaries (and to things you std::move).

Writing move operations

#include <algorithm>
#include <cstddef>
#include <iostream>
#include <utility>

class Buffer {                                  // owns a raw array (for teaching; prefer std::vector)
public:
    explicit Buffer(std::size_t n) : size_(n), data_(new int[n]{}) { std::cout << "allocate " << n << '\n'; }
    ~Buffer() { delete[] data_; }

    Buffer(const Buffer& other) : size_(other.size_), data_(new int[other.size_]) {    // copy constructor
        std::copy(other.data_, other.data_ + size_, data_);
        std::cout << "copy " << size_ << '\n';
    }
    Buffer(Buffer&& other) noexcept                                                   // move constructor
        : size_(std::exchange(other.size_, 0)), data_(std::exchange(other.data_, nullptr)) {
        std::cout << "move " << size_ << '\n';
    }
    Buffer& operator=(Buffer other) noexcept {   // copy-and-swap: handles copy AND move assignment
        std::swap(size_, other.size_);
        std::swap(data_, other.data_);
        return *this;
    }
    std::size_t size() const { return size_; }

private:
    std::size_t size_;
    int* data_;
};

Buffer make_buffer() { return Buffer(3); }      // returned without copying (copy elision)

int main() {
    Buffer a(5);
    Buffer b = a;                    // copy
    Buffer c = std::move(a);         // move
    Buffer d = make_buffer();        // no copy, no move
    b = Buffer(8);                   // move-assign a temporary
    std::cout << a.size() << ' ' << b.size() << ' ' << c.size() << ' ' << d.size() << '\n';
    return 0;
}

Output

allocate 5
copy 5
move 5
allocate 3
allocate 8
0 8 5 3

The move constructor just takes the other object's pointer and leaves it empty – constant time, whatever the size. Marking it noexcept matters: std::vector only uses moves when growing if they can't throw.

The rules of zero, three and five

RuleSays
Rule of zeroPrefer classes that manage no raw resources themselves – use vector, string and smart pointers as members, and write none of the special functions. The compiler generates correct ones.
Rule of three (pre-C++11)If you write a destructor, copy constructor or copy assignment, you probably need all three.
Rule of fiveIf you manage a resource by hand, write all five: destructor, copy constructor, copy assignment, move constructor, move assignment.
#include <iostream>
#include <memory>
#include <string>
#include <vector>

class Playlist {                     // rule of zero: no destructor, no copy/move code needed
public:
    explicit Playlist(std::string name) : name_(std::move(name)) {}
    void add(std::string song) { songs_.push_back(std::move(song)); }
    std::size_t size() const { return songs_.size(); }
    const std::string& name() const { return name_; }
private:
    std::string name_;
    std::vector<std::string> songs_;  // members clean up and copy/move themselves
};

int main() {
    Playlist p("Focus");
    p.add("Song A");
    p.add("Song B");
    Playlist backup = p;              // correct deep copy, generated automatically
    Playlist moved = std::move(p);    // correct move, generated automatically
    std::cout << backup.name() << ' ' << backup.size() << ' ' << moved.size() << '\n';
    return 0;
}

Output

Focus 2 2

Common mistakes with std::move

  • Using an object after moving from it as if it still held its value.
  • return std::move(local); – it prevents copy elision. Just return local;.
  • std::move on a const object silently copies, because you can't steal from const.

Practice

Quick check

Does std::move move anything?

No – it's a cast to an rvalue reference; the move constructor or assignment does the moving.

What state is an object in after being moved from?

Valid but unspecified – you can assign to it or destroy it, but shouldn't rely on its value.

What is the rule of zero?

Design classes so members manage resources, and write no destructor or copy/move functions yourself.

Why mark move constructors noexcept?

So containers like vector use moves (not copies) when they reallocate.

Summary

  • Moving transfers resources instead of copying; std::move marks an object as movable.
  • Write move operations (noexcept) only when you manage a raw resource – then follow the rule of five.
  • Prefer the rule of zero: build classes from vector, string and smart pointers.

Finished this lesson?

Mark it complete to track your progress, then carry on with the next lesson.

Next lesson

Popular