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.