Rule of Three in C++: What Every Developer Should Know

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

What is the Rule of Three in C++? Breaking It Down with Examples



If you have ever written a C++ class that manages dynamic memory (for example, using new or malloc), you have certainly encountered the Rule of Three. This is one of the most fundamental rules of the language, saving you from memory leaks, double deletions, and undefined behavior. Let's break it down with real examples so you don't just memorize the rule, but understand why it works.



Imagine: you wrote a class that allocates memory in the constructor. The compiler kindly generates the copy constructor and assignment operator for you. But it does this shallowly (shallow copy). And that's where the problems begin. The Rule of Three states: if your class explicitly defines a destructor, copy constructor, or assignment operator, then you most likely need to define all three. Why? Because this is a signal that your class manages some resource (memory, file, network connection), and copying must be deep.



The Problem: Shallow Copy

Let's look at a typical example. A String class that stores a C-string in dynamic memory:



class String {private:    char* data;    size_t size;public:    // Constructor    String(const char* str) {        size = strlen(str);        data = new char[size + 1];        strcpy(data, str);    }

// Destructor ~String() { delete[] data; }

// Copy constructor — generated by the compiler (shallow) // Assignment operator — generated by the compiler (shallow)

void print() const { std::cout << data << std::endl; }};


Everything looks fine until we try to copy the object:



int main() {    String s1("Hello");    String s2 = s1;  // Calls the copy constructor (generated)    s1.print();    s2.print();    // The program will crash with a double free error!    return 0;}


What happened? The compiler generated a copy constructor that simply copied the data pointer. Now s1.data and s2.data point to the same block of memory. When the objects go out of scope, their destructors are called. The first destructor frees the memory, the second tries to free it again — double free. The program crashes or behaves unpredictably.



The Solution: The Rule of Three in Action

To fix this situation, you need to implement all three special member functions. This is the Rule of Three. Let's write the correct version of the class:



class String {private:    char* data;    size_t size;public:    // Constructor    String(const char* str) {        size = strlen(str);        data = new char[size + 1];        strcpy(data, str);    }

// Destructor ~String() { delete[] data; }

// Copy constructor — deep copy String(const String& other) { size = other.size; data = new char[size + 1]; strcpy(data, other.data); }

// Assignment operator — deep copy String& operator=(const String& other) { if (this == &other) return *this; // self-assignment check

delete[] data; // free old resources

size = other.size; data = new char[size + 1]; strcpy(data, other.data);

return *this; }

void print() const { std::cout << data << std::endl; }};


Now copying works correctly: each object owns its own block of memory. Assignment is also

Blogs

Book Recommendations