Reading — step 1 of 6
Learn
When a class manages a resource (raw pointer, file handle, mutex, etc.), the compiler auto-generates copy/move constructors and destructors. The defaults are usually wrong for resource-managing classes — leading to double-frees, leaks, and dangling pointers.
The Rule of Five says: if you define ANY of these five, you usually need to define ALL of them.
The five special member functions
class Buffer {
public:
Buffer(size_t n) : data_(new int[n]), size_(n) {}
// 1. Destructor
~Buffer() { delete[] data_; }
// 2. Copy constructor
Buffer(const Buffer& other) : data_(new int[other.size_]), size_(other.size_) {
std::copy(other.data_, other.data_ + size_, data_);
}
// 3. Copy assignment
Buffer& operator=(const Buffer& other) {
if (this == &other) return *this;
delete[] data_;
size_ = other.size_;
data_ = new int[size_];
std::copy(other.data_, other.data_ + size_, data_);
return *this;
}
// 4. Move constructor
Buffer(Buffer&& other) noexcept : data_(other.data_), size_(other.size_) {
other.data_ = nullptr;
other.size_ = 0;
}
// 5. Move assignment
Buffer& operator=(Buffer&& other) noexcept {
if (this == &other) return *this;
delete[] data_;
data_ = other.data_;
size_ = other.size_;
other.data_ = nullptr;
other.size_ = 0;
return *this;
}
private:
int* data_;
size_t size_;
};
The Rule of Zero
If you can avoid managing resources directly — by using smart pointers, STL containers, etc. — you don't need ANY of the five. The compiler-generated defaults work correctly for classes whose members are themselves well-behaved.
class GoodBuffer {
public:
GoodBuffer(size_t n) : data_(n) {}
// No special functions needed — std::vector handles everything
private:
std::vector<int> data_;
};
Rule of Zero is the modern default. Reach for Rule of Five only when you absolutely have to manage a raw resource.
Why each function matters
Without proper copy ctor / copy assignment, copying a Buffer copies the POINTER — both objects now own the same array. When the first goes out of scope and deletes, the second is dangling. Crash.
Without proper move ctor / move assignment, returning a Buffer by value forces a copy when it could have been a cheap move (just transfer the pointer).
= default and = delete
You can explicitly opt in or out:
class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete; // can't copy
NonCopyable& operator=(const NonCopyable&) = delete; // can't assign
NonCopyable(NonCopyable&&) = default; // but can move
NonCopyable& operator=(NonCopyable&&) = default;
};
Classic use: file handles, mutexes, sockets — things where copying makes no sense.
Common mistakes
- Defining only the destructor — copy still uses the default (shallow). Either delete the copy or write it.
- Forgetting
noexcepton move ops — STL containers may fall back to copying for exception safety. - Self-assignment in copy assignment — guard with
if (this == &other) return *this;. - Manually managing memory when std::vector / std::string / smart_ptr would do — you're writing the Rule of Five for nothing.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…