The Rule Of Five In C++ - C++ Programming Tutorial
C++ Course / Miscellaneous / The Rule Of Five In C++

The Rule Of Five In C++

BLUF: Mastering The Rule Of Five In C++ is a critical step in becoming a proficient C++ developer. This lesson provides a deep dive into the syntax, performance considerations, and real-world applications of this concept.
Key Performance Insight: The Rule Of Five In C++

C++ is renowned for its efficiency. Learn how The Rule Of Five In C++ enables low-level control and high-performance computing in the tutorial below.

Within this guide, you will acquire knowledge regarding the Laws of Five in C++ alongside their syntax and illustrations.

The Rule of Five states that if your class needs any of the following, it probably needs all of them:

  • Destructor: It is used to avoid resource leaks when an object goes out of scope.
  • Copy Constructor: It is used to properly copy class resources when an object is copied.
  • Copy assignment operator: It is used to copy resources when one object is assigned to another.
  • Move Constructor: It is used to efficiently move resources, rather than copy when an object is moved.
  • Move assignment operator: It is used to move resources when one object is moved into another.

Essentially, the guideline indicates that when your class handles resources such as pointers or file handles, it is essential to correctly develop the constructors, destructor, and assignment operators to prevent problems with managing those resources. Adhering to the guideline helps in avoiding resource leaks, invalid states of objects, and redundant copying.

The importance of C++'s Rule of Five

The Rule of Five exists in C++ due to the need to properly manage resources like pointers, file handles, and dynamic memory allocations. Here are some key reasons why following the Rule of Five is important:

  • Prevent resource leaks: The destructor allows objects to free/close resources when they go out of scope. Without a destructor, resources may be leaked.
  • Avoid invalid states after copying: The copy constructor and assignment operators properly copy resource ownership from one object to another. Without them, copies of an object could end up with broken/invalid pointers.
  • Improve efficiency through move semantics: Move constructors and move assignment operators transfer resources instead of copying them, which is faster.
  • Consistency: If a class needs one special method, like a destructor or copy constructor, it likely needs the rest to work correctly. The rule sets consistent expectations.
  • Encapsulation: The rule encapsulates resource handling neatly inside the class, preventing issues from incorrectly using/copying objects.

It defines established guidelines for managing resources within classes, promoting accurate program functionality and preventing challenging bugs. Adhering to the five conventional techniques guarantees appropriate resource management during object initialization, destruction, copying, or movement.

1. Destructor:

A destructor is a special function called automatically when an object is being destroyed or goes out of scope. Its primary role is to release any resources that the object might have allocated while it was in use.

Syntax:

It has the following syntax:

Example

class MyClass {
    public:
        // Constructor
        MyClass() {
            //Initialization code
        }

        // Destructor
        ~MyClass() {
            // Release resources
        }
    };
  • Destructors are named after the class they belong to, with a tilde (~) placed before the class name.
  • Destructors do not accept any arguments and do not specify a return type.

Example:

Let's consider a scenario to demonstrate the destructor in C++.

Example

class File {
  private:
    HANDLE fileHandle;
  
  public:
    File() {
      fileHandle = CreateFile(...);
    }
    
    ~File() {
      CloseHandle(fileHandle); // release resource
    }
}; 

File f1;   // destructor called automatically when
              // f1 goes out of scope to close the file handle

In this instance, the destructor is responsible for shutting down the file handle that was initially opened by the class to avoid any potential resource leaks upon the object's destruction. The management of resources is effectively contained within the class through the implementation of the destructor.

2. Copy Constructor:

A duplication constructor is a special kind of constructor that leverages an already-present object of the identical class to set up a new object. Its purpose is to set up fresh objects based on existing ones.

Syntax:

It has the following syntax:

Example

class MyClass {
    public:
        // Copy Constructor
        MyClass(const MyClass& other) {
            // Copy resources from 'other' to 'this'
        }
    };
  • It is a constructor that takes a reference to an object of the same class as a parameter.
  • It has a single parameter that references the same class type.
  • The parameter must be passed by reference and not by value.
  • It is generally used to deep copy objects during initialization.

Example:

Let's consider an example to demonstrate the copy constructor in C++.

Example

class Person {
  string name;
  int age; 

public:
  // regular constructor
  Person(string n, int a) {
    name = n;
    age = a;  
  }

  // copy Constructor  
  Person(const Person &person) {
    name = person.name; 
    age = person.age;  
  }
};

Person p1("John", 22);

// Copy Constructor called
Person p2 = p1; // p2 initialized as copy of p1

In this instance, the copy constructor sets up p2 as a duplicate of p1 by replicating its name and age attributes, thereby avoiding shallow object copying.

3. Copy Assignment Operator:

The process of the copy assignment operator involves transferring values from one instance to another within the same class. It is responsible for assigning the content of one object to a different object of identical class.

Syntax:

It has the following syntax:

Example

class MyClass {
    public:
        // Copy Assignment Operator
        MyClass& operator=(const MyClass& other) {
            if (this != &other) {
                // Release resources
                // Copy resources from 'other' to 'this'
            }
            return *this;
        }
    };
  • It overloads the = operator.
  • It has a parameter that refers to an object of the same class.
  • The parameter must be passed by reference.
  • It assigns the data members of the passed object to the current object.
  • It returns a reference to the current object (*this).

Example:

Let's consider a scenario to demonstrate the copy assignment operator in C++.

Example

class Car {
   string make;
   int year;

public:
  // Copy assignment operator
  Car& operator=(const Car &c) {
    make = c.make;  
    year = c.year;
    return *this;
  }
};

Car c1("Toyota", 2022); 
Car c2("Ford", 2020);

c2 = c1;  // Assign c1 values to c2  
              // Using copy assignment operator

In this instance, the copy assignment operator is redefined to transfer c1's values to the current c2 object. This operation duplicates the member data and yields the modified object.

4. Move Constructor:

A move constructor shifts ownership of resources from one object to another instead of duplicating them. This is essential for incorporating move semantics, which enhances resource transfer speed compared to copying.

Syntax:

It has the following syntax:

Example

class MyClass {
    public:
        // Move Assignment Operator
        MyClass& operator=(MyClass&& other) noexcept {
            if (this != &other) {
                // Release resources
                // Transfer ownership of resources from 'other' to 'this'
            }
            return *this;
        }
    };

Example:

Let's consider an example to demonstrate the move constructor in C++.

Example

class Vector {
  int *arr;
  int size;

public:
  // Move Constructor  
  Vector(Vector &&x) {
    arr = x.arr;
    size = x.size;

    x.arr = nullptr; // Leave x in valid state
  }
};

Vector v1(100); 
Vector v2 = std::move(v1); // Move v1 resources to v2

Here, the move constructor effectively transfers the data pointer from v1 to v2. v1 is placed in a valid null state post the move operation. As a result, resources are transferred rather than duplicated, facilitating streamlined move semantics.

5. Move Assignment Operator:

The move assignment operator shifts resources from one object to another instead of duplicating them. This operator is essential for implementing move semantics, enabling quicker resource transfers compared to copying.

Syntax:

It has the following syntax:

Example

ClassName& operator=(ClassName &&old_obj) {
  // Transfer ownership of data from old_obj 
  // to the current object

  // Return *this
}
  • It overloads the = operator , similar to copy assignment.
  • It takes an rvalue reference (&&) to the same class type.
  • Resources and data members are transferred from the old_obj object to the current object.
  • The old_obj is left in a valid but unspecified state.
  • It returns a reference to the current object (*this).

Example:

Let's consider a scenario to demonstrate the application of the move assignment operator in the C++ programming language.

Example

class String {
  char *str;
  int len;
  
public:
  // Move assignment    
  String& operator=(String &&s) {
    if (this != &s) { 
      delete [] str;
  
      str = s.str;
      len = s.len;

      s.str = nullptr;
      s.len = 0;
    }
    return *this;
  }  
};

String s1 = "Hello";
String s2 = "World"; 

s2 = std::move(s1);  // Move s1 resources to s2

Here, the move assignment operator effectively shifts the data pointer from s1 to s2 rather than duplicating the data. This leaves s1 in a valid empty state post-move. As a result, it facilitates the transfer of resources instead of duplicating them, thus enabling move semantics.

Example:

Let's consider a C++ code example showcasing the implementation of the five special member functions: Destructor, Copy Constructor, Copy Assignment Operator, Move Constructor, and Move Assignment Operator.

Example

#include <iostream>
#include <utility>  // for std::move

class ResourceHolder {
public:
    // Constructor
    ResourceHolder(std::size_t size) : size_(size), data_(new int[size]) {
        std::cout << "Constructor - Allocated memory for " << size << " integers.\n";
    }

    // Destructor
    ~ResourceHolder() {
        delete[] data_;
        std::cout << "Destructor - Deallocated memory.\n";
    }

    // Copy Constructor
    ResourceHolder(const ResourceHolder& other) : size_(other.size_), data_(new int[other.size_]) {
        std::cout << "Copy Constructor - Copied " << size_ << " integers.\n";
        std::copy(other.data_, other.data_ + size_, data_);
    }

    // Copy Assignment Operator
    ResourceHolder& operator=(const ResourceHolder& other) {
        if (this != &other) {
            delete[] data_;
            size_ = other.size_;
            data_ = new int[size_];
            std::cout << "Copy Assignment Operator - Copied " << size_ << " integers.\n";
            std::copy(other.data_, other.data_ + size_, data_);
        }
        return *this;
    }

    // Move Constructor
    ResourceHolder(ResourceHolder&& other) noexcept : size_(0), data_(nullptr) {
        swap(*this, other);
        std::cout << "Move Constructor - Moved resources.\n";
    }

    // Move Assignment Operator
    ResourceHolder& operator=(ResourceHolder&& other) noexcept {
        if (this != &other) {
            delete[] data_;
            size_ = 0;
            data_ = nullptr;
            swap(*this, other);
            std::cout << "Move Assignment Operator - Moved resources.\n";
        }
        return *this;
    }

    // Swap function for efficient swapping
    friend void swap(ResourceHolder& first, ResourceHolder& second) noexcept {
        using std::swap;
        swap(first.size_, second.size_);
        swap(first.data_, second.data_);
    }

private:
    std::size_t size_;
    int* data_;
};

int main() {
    // Create an object using the Constructor
    ResourceHolder obj1(5);

    // Copy Constructor
    ResourceHolder obj2 = obj1;

    // Copy Assignment Operator
    ResourceHolder obj3(3);
    obj3 = obj1;

    // Move Constructor
    ResourceHolder obj4 = std::move(obj3);

    // Move Assignment Operator
    ResourceHolder obj5(2);
    obj5 = std::move(obj2);

    return 0;
}

Output:

Output

Constructor - Allocated memory for 5 integers.
Copy Constructor - Copied 5 integers.
Constructor - Allocated memory for 3 integers.
Copy Constructor - Copied 3 integers.
Move Constructor - Moved resources.
Constructor - Allocated memory for 2 integers.
Move Assignment Operator - Moved resources.
Destructor - Deallocated memory.
Destructor - Deallocated memory.
Destructor - Deallocated memory.
Destructor - Deallocated memory.
Destructor - Deallocated memory.

Input Required

This code uses input(). Please provide values below:

Logic Practice
Install Logic Practice
Add to home screen for a faster app-like experience