The Rule of Five in C++ | Explained

The Rule of Five is a programming concept brought about in C++11. It originates from the Rule of Three, where the introduction of Move Semantics in C++11, caused the Rule Of Three to expand and become the Rule Of Five.

Before we talk about the Rule of Five however, we will briefly talk about the Rule Of Three to understand it’s base.


What is the Rule of Three?

The Rule of Three states, that if a Class explicitly defines any of the following special member functions:

  1. Destructor
  2. Copy Constructor
  3. Copy Assignment Constructor

Then it is required to define the other two along with it. In short, if a Class explicitly defines the Destructor, then it should explicitly define the Copy Constructor and the Copy Assignment Constructor too..

By “explicitly” defined, we mean when the User himself defines the Member Function. Don’t forget that there are already default implementations of these member functions that exist. But these default versions only perform the very basics, and not suitable for many scenarios.

An example of where the Rule Of Three comes into play, is when we are dealing with Dynamic Memory in a Class. The Class must create the Destructor in order to free up that memory. It must also create the Copy Constructor and Copy Assignment Constructor to avoid running into the Shallow Copy issue. (The default implementation will only copy the pointer to the memory, not the actual contents. This results in both objects pointing to the same memory).


The Rule of Five in C++

Instead of just three Special Member functions, the Rule Of Five now encompasses a total of Five Special Member Functions that need to be defined in the event that any of them is required to be defined.

  1. Destructor
  2. Copy Constructor
  3. Copy Assignment Operator
  4. Move Constructor
  5. Move Assignment Operator

The first three are the same as the Rule Of Three, and have the same reasoning. In the next discussion we will discuss the two new additions.


Move Semantics

The two new additions are the Move Constructor and Move Assignment Constructor, which were introduced in C++11 (also known as Move Semantics). This concept was introduced to deal with the inefficiency introduced by Copy Constructors when dealing with memory.

To understood this concept, let’s imagine a situation where we have made the following assumptions:

  1. Let’s assume we have some memory (e.g: A custom String Class) that we have allocated in the main() function.
  2. We also have a Class A, to which we want to copy over this memory.
  3. We don’t need this memory in main(), and the only purpose of creating this was to copy it over into Class A.

The code for this looks something like this:

int main() {
     ClassA objA = ClassA( String("Hello World");
}

The problem with the above approach, is that we will end up with two instances of the same memory. The string “Hello World” will first be allocated in the main() function, and then copied into Class A. When Class A copies it, it will create new memory for the String. This is the nature of Copy Constructors, which looks something like this:

    String(const String& str) {   // Copy Constructor
        cout << "Copy Constructor Called\n";
        _size = str._size;
        _data = new char[_size];
        memcpy(_data, str._data, _size);
    }

Now this is a problem, as memory is allocated twice, and also deallocated twice. Just to reiterate, the first Allocation takes place in the main function, and the second takes place in the Copy Constructor of the Class A when creating the new object.

Move Constructors on the other hand, actually “move” the memory, instead of copying it. See the example in the next section on how Move Constructors are actually implemented.

It is important to note, that Move Constructors are not a replacement for Copy Constructors. If you need an actual Copy of the memory, then you will need to use a Copy Constructor. Only when we literally want to “move” the memory (i.e. Assumption number 3 that we made), do we use Move Constructors.


Rule of Five – Example

Let’s take a look at an actual real-life example, regarding the creation of a String Class in C++. This example follows the Rule of Five, and has all Five Special Member Functions available in it. If you run the following code, you will also notice several of these Member Functions being used.

#include <iostream>
#include <cstring>
using namespace std;


class String {
    char * _data;
    int _size;

public:
    String(const char* data) {   // Constructor
        cout << "Created" << endl;
        _size = strlen(data);
        _data = new char[_size];
        memcpy(_data, data, _size);
    }

    String(String&& str) {   // Move Constructor
        cout << "Move Constructor Called" << endl;
        _size = str._size;
        _data = str._data;

        str._data = nullptr;
    }

    String& operator=(String&& str) {   // Move Assignment Constructor
        if (this != &str) {
            delete[] _data;    
            _data = str._data;  
            str._data = nullptr;  
        }
        return *this;
    }

    String& operator=(const String& str) {
        if (this == &str) return *this;

        delete[] _data;
        _data = NULL;
        _size = str._size;
        _data = new char[_size];
        memcpy(_data, str._data, _size);

        return *this;
    }

    String(const String& str) {   // Copy Constructor
        cout << "Copy Constructor Called\n";
        _size = str._size;
        _data = new char[_size];
        memcpy(_data, str._data, _size);
    }

    ~String() {    // Destructor
        cout << "Destructor Called\n";
        delete[] _data;
    }

    void Print() const {
        for (size_t i = 0; i < _size; i++)
            cout << _data[i];
        cout << endl;
    }
};


class Person {
    String _name;

public:
    Person(const String& s) : _name(s) { }
    Person(String&& s) : _name(std::move(s)) { }

    void Print) {
        _name.Print();
    }
};

int main() {
    Person person(String("CodersLegacy"));
    person.Print();
}

References

The above example (and by extension, the Rule of Five) relies heavily on the concept of Move Semantics. If you wish to learn more about this concept, check out our dedicated tutorial on Move Semantics in C++.

If you want to know more about the various types of Constructors & Assignments Operators, refer to our dedicated tutorial on the topic.


This marks the end of the “Rule of Five in C++” Tutorial. Any suggestions or contributions for CodersLegacy are more than welcome. Questions regarding the tutorial content can be asked in the comments section.

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments