Modern C++: The Rule of Zero or Five
This blog post is part of the series Modern C++.
It is highly recommended to read You Don’t Need Move Constructors along with this one.
Introduction
Hey there, C++ adventurers! If you’re diving into the world of classes, object ownership, and resource management, you’ve probably stumbled upon the enigmatic “rule of zero” and “rule of five.” These guidelines aren’t some ancient C++ decree carved in stone tablets, but rather handy principles to help you write safer and more efficient code. Let’s unravel these rules and see how they apply in modern C++.
Once Upon a Time: The Rule of Three
Before we jump into the present, let’s rewind to the pre-C++11 era. Back then, the “rule of three” reigned supreme. It stated that if you needed to define a custom:
- Destructor: Cleans up resources when an object is destroyed
- Copy constructor: Creates a new object as a copy of an existing one
- Copy assignment operator: Assigns the values from one object to another
then you likely needed to define all three to ensure your class behaved consistently and avoided memory leaks.
Enter the Rule of Five
With the arrival of C++11, two new special member functions joined the party:
- Move constructor: (Transfers ownership of resources from one object to another)
- Move assignment operator: (Similar to move constructor, but for assignment)
Now, the “rule of three” evolved into the “rule of five.” If you defined any one of these five functions, it was best practice to define them all to maintain proper resource management and avoid surprises.
Modern C++ Zen: The Rule of Zero
Here’s where things get interesting. Modern C++ encourages a simpler approach: the “rule of zero.” The idea is to avoid defining any of the special member functions manually if possible. Instead, rely on the compiler-generated defaults, which are often well-optimized and correct for simple classes.
When does this work? Typically, if your class doesn’t manage any resources directly (like raw pointers or file handles) and relies on RAII (Resource Acquisition Is Initialization) types (e.g., std::string
, std::vector
), you can often embrace the rule of zero. This leads to less code to write and maintain.
Example
Rule of Zero in Action
class Person {
public:
std::string name;
int age;
};
// No need to define any special member functions!
The Rule of Five: When It Still Matters
Hold on — the rule of five isn’t obsolete! If your class does manage resources directly, or if you have specific customization needs for copying or moving objects, then the rule of five still holds. You’ll need to carefully define these functions to ensure correct behavior.
#include <iostream>
class DynamicArray {
public:
DynamicArray(int size) : size(size), data(new int[size]) {}
// Destructor
~DynamicArray() {
delete[] data;
}
// Copy constructor
DynamicArray(const DynamicArray& other)
: size(other.size), data(new int[other.size]) {
std::copy(other.data, other.data + size, data);
}
// Copy assignment
DynamicArray& operator=(const DynamicArray& other) {
if (this != &other) {
delete[] data;
size = other.size;
data = new int[size];
std::copy(other.data, other.data + size, data);
}
return *this;
}
// Move constructor
DynamicArray(DynamicArray&& other) noexcept
: size(other.size), data(other.data) {
other.size = 0;
other.data = nullptr;
}
// Move assignment
DynamicArray& operator=(DynamicArray&& other) noexcept {
if (this != &other) {
delete[] data;
size = other.size;
data = other.data;
other.size = 0;
other.data = nullptr;
}
return *this;
}
private:
int size;
int* data;
};
- Rule of Zero: Strive for it when possible. Let the compiler do the heavy lifting!
- Rule of Five: Embrace it when you need fine-grained control over resource management.
Modern C++ gives you powerful tools to write clean, efficient, and safe code. By understanding these rules, you’re well on your way to becoming a C++ resource management maestro!