C++ Rule Of Three.



The rule of three in C++ states that, if a class in C++ has any one (or more) of the following, then it should define all three.

These three are special member functions of class and are responsible for managing resources such as dynamic memory, file handles, sockets, etc. And if one of them is defined explicitly, means that class is managing those resources manually (like memory using new/delete), and if we fail to define others then it can lead to memory leaks, shallow copies, double deletions and undefined behavior. This is also known as the Law of Big Three or The Big Three.

When you explicitly define one of these functions, then the compiler still automatically provides default (implicit) versions of others. But there are most chances of those two to be incorrect or unsafe because they perform shallow copies (which only copy pointers) not the actual data they point to. And when both objects (original and copied) go out of scope, the destructor runs for both, and tries to delete the same pointer, which leads to double deletion and causes a runtime error or undefined behavior.

C++ Example Demonstrating the Rule of Three

Here is the following example code of implementing a full rule of three in C++ that's constructor, copy constructor, copy assignment operator, and destructor showcasing how all are defined to properly manage dynamic memory and ensuring deep copy to prevent issues like shallow copy, memory leaks, and double deletion.

#include <iostream>
using namespace std;

class Numbers {
private:
    int num;
    int* ptr;

public:
    // this is a constructor, which allocates dynamic memory 
    // and performs deep copy of passed array
    Numbers(int n, int* p) {
        num = n;
        ptr = new int[num];
        for (int i = 0; i < num; ++i) {
            ptr[i] = p[i];
        }
        cout << "Constructor called\n";
    }

    // a copy constructor (part of Rule of Three), 
    // this performs deep copy to prevent two 
    // objects pointing to same memory
    Numbers(const Numbers& other) {
        num = other.num;
        ptr = new int[num];
        for (int i = 0; i < num; ++i) {
            ptr[i] = other.ptr[i];
        }
        cout << "Copy Constructor called\n";
    }

    // copy assignment operator (Rule of Three)
    Numbers& operator=(const Numbers& other) {
        if (this != &other) { // self-assignment check and then delete current memory
            delete[] ptr; 

            num = other.num;
            ptr = new int[num];
            for (int i = 0; i < num; ++i) {
                ptr[i] = other.ptr[i];
            }
        }
        cout << "Copy Assignment Operator called\n";
        return *this;
    }

    // a destructor (again a part of Rule of Three), 
    // which frees the dynamically allocated memory
    ~Numbers() {
        delete[] ptr;     
        ptr = nullptr;   
        cout << "Destructor called\n";
    }
};

int main() {
    int arr[4] = {11, 22, 33, 44};

    Numbers Num1(4, arr);     // Constructor is called
    Numbers Num2(Num1);       // Copy Constructor is called (deep copy)

    Num2 = Num1;              // Copy Assignment Operator is called

    return 0;                 // Destructor called for Num2 and Num1 (in reverse order)
}

Output

Constructor called
Copy Constructor called
Copy Assignment Operator called
Destructor called
Destructor called
Akansha Kumari
Akansha Kumari

Hi, I am Akansha, a Technical Content Engineer with a passion for simplifying complex tech concepts.

Updated on: 2025-07-15T17:12:43+05:30

839 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements