Lesson 11 / 25

Copy and Move Semantics and the Rule of Zero, Three and Five

Understand copying, moving and which special members a class needs.

What happens when objects are copied or moved

Every class has special member functions the compiler can generate: default constructor, copy constructor, copy assignment, move constructor, move assignment and destructor. Copying duplicates an object's state; moving (C++11) transfers resources from an object that is about to disappear, such as a temporary, leaving it in a valid but unspecified (usually empty) state: moving a std::vector just steals its buffer pointer instead of copying every element. std::move(x) does not move anything itself; it casts x to an rvalue reference, allowing a move to be selected. The Rule of Zero: if your class only holds members that manage themselves (std::string, std::vector, smart pointers), write none of the special members; the generated ones do the right thing. The Rule of Three (classic) and Rule of Five (modern): if you write a destructor, copy constructor or copy assignment because the class manages a raw resource, you probably need all of them, plus the two move operations. Prefer the Rule of Zero by wrapping raw resources in RAII types.

Rule of zero versus a hand-written resource class

The first class needs no special members; the second needs all five.

#include <algorithm>
#include <cstddef>
#include <memory>
#include <string>
#include <utility>
#include <vector>

// Rule of Zero: members manage themselves; copies and moves just work
struct Student {
    std::string name;
    std::vector<int> marks;
};

// Rule of Five: owns a raw buffer (shown for learning; prefer std::vector)
class Buffer {
public:
    explicit Buffer(std::size_t n) : size_(n), data_(new int[n]{}) {}
    ~Buffer() { delete[] data_; }

    Buffer(const Buffer& other) : size_(other.size_), data_(new int[other.size_]) {      // copy
        std::copy(other.data_, other.data_ + size_, data_);
    }
    Buffer& operator=(Buffer other) noexcept {      // copy-and-swap handles copy and move assignment
        swap(other);
        return *this;
    }
    Buffer(Buffer&& other) noexcept                  // move: steal the pointer
        : size_(std::exchange(other.size_, 0)), data_(std::exchange(other.data_, nullptr)) {}

    void swap(Buffer& o) noexcept { std::swap(size_, o.size_); std::swap(data_, o.data_); }

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

Student a{"Asha", {91, 88}};
Student b = a;              // copy
Student c = std::move(a);   // move: a.name and a.marks are left valid but unspecified (typically empty)

Photocopying versus handing over a file

Copying a folder means photocopying every page. Moving it means handing the original folder across the desk: instant, and you are left with an empty space where it was.

Quick check: What does std::move actually do?

  • It copies the object to the heap
  • It casts its argument to an rvalue reference so that a move operation can be chosen
  • It deletes the original object
  • It always makes code faster
Answer

It casts its argument to an rvalue reference so that a move operation can be chosen — std::move is a cast; the move constructor or assignment performs the actual transfer.