Modern C++: The Rule of Zero or Five

Dagang Wei
3 min readJun 25, 2024

--

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!

--

--