Rule of Three/Five
Master the Rule of Three/Five for complete resource management in C++ classes
Learn the Rule of Three/Five - the foundation of correct resource management in C++ through special member functions.
A Simple Example
#include <iostream>
#include <utility>
class Resource {
private:
int* data;
int size;
public:
Resource(int s) : size{s} {
data = new int[size];
std::cout << "Constructor (" << size << " ints)" << "\n";
}
~Resource() {
std::cout << "Destructor" << "\n";
delete[] data;
}
Resource(const Resource& other) : size{other.size} {
data = new int[size];
for (int i{0}; i < size; ++i) {
data[i] = other.data[i];
}
std::cout << "Copy constructor" << "\n";
}
Resource& operator=(const Resource& other) {
if (this != &other) {
delete[] data;
size = other.size;
data = new int[size];
for (int i{0}; i < size; ++i) {
data[i] = other.data[i];
}
std::cout << "Copy assignment" << "\n";
}
return *this;
}
Resource(Resource&& other) noexcept : data{other.data}, size{other.size} {
other.data = nullptr;
other.size = 0;
std::cout << "Move constructor" << "\n";
}
Resource& operator=(Resource&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
std::cout << "Move assignment" << "\n";
}
return *this;
}
void display() const {
std::cout << "Resource with " << size << " ints" << "\n";
}
};
int main() {
Resource r1{100};
Resource r2 = r1;
Resource r3{200};
r3 = r1;
Resource r4 = std::move(r1);
Resource r5{300};
r5 = std::move(r2);
return 0;
}
Breaking It Down
The Rule of Three
- Three special members: Destructor, copy constructor, copy assignment operator
- Rule: If you define one, you probably need all three
- Why: They work together to manage resource ownership
- Remember: Rule of Three is for C++98/03 and basic resource management
The Rule of Five
- Adds two more: Move constructor and move assignment operator
- Why: Enables efficient transfer of resources without copying
- When: C++11 and later, for performance-critical code
-
Remember: Move operations should be
noexceptfor optimal performance
Destructor
- Purpose: Release resources when object is destroyed
-
Example:
delete[] data;frees allocated memory - Always called: Automatically when object goes out of scope
- Remember: Critical for preventing resource leaks
Copy Operations
-
Copy constructor:
Resource(const Resource& other)- deep copy for initialization -
Copy assignment:
Resource& operator=(const Resource& other)- deep copy for existing object - Both need: Self-assignment check, deep copy of resources, return *this
- Remember: Copying is expensive but necessary for safety
Move Operations
-
Move constructor:
Resource(Resource&& other) noexcept- steal resources -
Move assignment:
Resource& operator=(Resource&& other) noexcept- steal and cleanup -
Both need: Transfer ownership, leave source in valid but empty state,
noexcept - Remember: Moving is cheap - just pointer swapping, no deep copy
Why This Matters
- The Rule of Three/Five isn't just a guideline - it's the foundation of correct resource management in C++.
- If your class manages a resource (memory, files, locks), you need these special member functions working together correctly. Get it wrong and you have memory leaks, double deletions, or crashes. Get it right and you have robust, efficient code.
- This is one of C++'s most important idioms, distinguishing C++ from garbage-collected languages.
Critical Insight
You can opt out! If your class doesn't manage resources directly, you don't need the Rule of Three/Five - the compiler-generated defaults are perfect. This is the "Rule of Zero" - prefer using smart pointers and STL containers that handle resources for you. Only implement special member functions when you're managing raw resources:
// Rule of Five - manual resource management
class Manual {
int* data;
// Need all 5 special functions
};
// Rule of Zero - let standard library handle it
class Modern {
std::unique_ptr<int[]> data; // Handles everything!
// Compiler-generated functions work perfectly
};
Best Practices
Prefer Rule of Zero: Use smart pointers and STL containers instead of manual resource management when possible.
Mark move operations noexcept: This enables optimizations in STL containers and ensures strong exception safety.
Always check self-assignment: In copy and move assignment operators, check if (this != &other) first.
Leave moved-from objects valid: After move, the source object should be in a valid but unspecified state (usually empty).
Common Mistakes
Forgetting copy assignment: Having destructor and copy constructor but not copy assignment causes bugs.
Not marking move noexcept: Without noexcept, STL containers can't use move operations optimally.
Invalid moved-from objects: After moving, source object must still be destructible - don't leave dangling pointers.
Mixing manual and automatic: Using both raw pointers and smart pointers in the same class is error-prone.
Debug Challenge
This class is missing a critical function. Click the highlighted line to add it:
Quick Quiz
- What are the three functions in the Rule of Three?
- What does the Rule of Five add to the Rule of Three?
- When can you use the Rule of Zero?
Practice Playground
Time to try out what you just learned! Play with the example code below, experiment by making changes and running the code to deepen your understanding.
Output:
Error:
Lesson Progress
- Fix This Code
- Quick Quiz
- Practice Playground - run once