Move constructor and move assignment — Rule of Five
WHY does move exist at all?
WHAT is being optimized: copying an O(N) heap buffer is replaced by copying an O(1) pointer.
HOW the compiler knows it's safe: the source is an rvalue (a temporary or something you explicitly cast with std::move). Rvalues have no name you can use again, so stealing from them is safe.
The five special members
Deriving the move constructor from scratch
Let's build a minimal Buffer owning a heap array.
class Buffer {
int* data_;
size_t size_;
public:
// 0. Constructor
Buffer(size_t n) : data_(new int[n]{}), size_(n) {}
// 1. Destructor
~Buffer() { delete[] data_; }
// 2. Copy constructor — DEEP copy
Buffer(const Buffer& o) : data_(new int[o.size_]), size_(o.size_) {
std::copy(o.data_, o.data_ + o.size_, data_); // Why? source must survive intact
}
// 3. Copy assignment — copy-and-swap idiom
Buffer& operator=(const Buffer& o) {
Buffer tmp(o); // Why? reuse copy ctor; strong exception safety
swap(*this, tmp); // Why? swap is noexcept; tmp's dtor frees our old data
return *this;
}
// 4. Move constructor — STEAL
Buffer(Buffer&& o) noexcept
: data_(o.data_), size_(o.size_) { // Why? grab the pointer, no allocation
o.data_ = nullptr; // Why? so o's dtor won't free what we now own
o.size_ = 0;
}
// 5. Move assignment — STEAL into existing
Buffer& operator=(Buffer&& o) noexcept {
if (this != &o) { // Why? guard against self-move
delete[] data_; // Why? free our current resource first
data_ = o.data_; // steal
size_ = o.size_;
o.data_ = nullptr; // leave source in valid empty state
o.size_ = 0;
}
return *this;
}
friend void swap(Buffer& a, Buffer& b) noexcept {
std::swap(a.data_, b.data_);
std::swap(a.size_, b.size_);
}
};
Worked examples
Flashcards
What does a move constructor steal and what must it leave behind?
State the Rule of Five.
What does std::move(x) actually do at runtime?
Why must move operations be marked noexcept?
What bug occurs if you write a destructor but rely on the implicit copy constructor for a pointer member?
Why null out the source in a move?
What is the signature difference between copy and move constructor?
T(const T&). Move: T(T&&) noexcept.Why include a self-assignment check in move assignment?
x = std::move(x) happens (often via generic algorithms).Recall Feynman: explain to a 12-year-old
Imagine you're moving house. Copying = buying all-new furniture identical to the old, then throwing the old away. Wasteful! Moving = the old house is being demolished anyway, so just carry your existing furniture to the new house and leave the old one empty so the wrecking crew doesn't smash your sofa. The "Rule of Five" is the checklist of five chores you must finish so nobody ends up with two families owning the same sofa (which would cause a fight = a crash).
Connections
- Rule of Three — the pre-C++11 ancestor (D, copy-ctor, copy-assign)
- Rule of Zero — prefer RAII members so you write none of the five
- std::move and rvalue references — the cast that enables moves
- Copy elision and RVO — when even the move is skipped
- Smart pointers (unique_ptr / shared_ptr) — move-only & RAII resource owners
- Exception safety guarantees — why
noexceptand copy-and-swap matter
Concept Map
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Dekho, jab tum ek bada object (jaise heap pe allocated buffer wali class) ko copy karte ho, toh purana tarika ye tha: naya memory allocate karo, saara data byte-by-byte copy karo — ye O(N) ka mehnga kaam hai. Lekin agar source ek temporary hai jo waise bhi marne wala hai (rvalue), toh itni mehnat kyun? Move ka idea simple hai: source ka pointer cheen lo (steal), aur source ka pointer ko nullptr kar do (sterilize) taaki uska destructor tumhari memory free na kar de. Bas — O(1) mein kaam ho gaya.
Rule of Five kehta hai: agar tumne destructor, copy constructor, ya copy assignment mein se koi ek bhi khud likha, matlab tum manually resource manage kar rahe ho — toh paanchon special members likho: destructor, copy ctor, copy assignment, move ctor, aur move assignment. Agar sirf destructor likha aur baaki compiler pe chhod diya, toh do problem: (1) implicit copy shallow pointer copy karega → double free crash, aur (2) move members generate hi nahi honge, sab kuch slow copy ban jayega.
Ek important baat: std::move(x) khud kuch move nahi karta! Wo bas x ko rvalue reference mein cast karta hai, taaki compiler move version chun sake. Asli "chori" toh move constructor ke andar hoti hai. Aur move operations ko hamesha noexcept mark karo — warna std::vector reallocation ke waqt tumhare move ko ignore karke copy use karega (strong exception guarantee ke liye).
Yaad rakhne ke liye: "Steal, then Sterilize" — pointer chura lo, source ko khaali kar do. Self-move check (this != &o) bhi rakho, kyunki sort jaise algorithms mein wo case aa jata hai.