The rule of five

C++ supports Object Oriented Programming which means that you can express entities with classes and structures (struct) and make instances of them.

These classes group the operations available to work with the data the classes and structs encapsulate.

In a class, data is represented by members which are fields that contain data expressed in their data types and member functions which are the operations on that data.

Classes get constructed and destroy. If the Class is not complex, it only has fields of primitive types and it doesn’t allocate memory in the heap, C++ provides us with implicit constructors and destructors.

These constructors and destructors are methods that are run on construction and destruction respectively.

These methods constructor and destructor are special member functions that call just like the class and in the case of the destructor it’s preceded by the tilde character.

If we ever need to implement one of these methods we must know that we can implement many different constructors but only one destructor.

Constructors can have parameters or not have them at all.

There are 3 types of constructors though.

The first type is a constructor to create an instance out of parameters or without them.

class MyClass
{
public:
	MyClass(){/* your implementation */}

	MyClass(int param1, int param2){/* your implementation */}
}

MyClass my_object(1, 2);

A Second type of constructor is called copy constructor.

class MyClass
{
public:
	MyClass(const MyClass& inst){/* your implementation */}
}

MyClass my_object;


MyClass another_object(my_object);

This is a special type which accepts as only parameter an instance of the same type and it’s mean to copy that object into the object we are instantiating.

The third type is the move constructor.

class MyClass
{
public:
	MyClass(MyClass&& inst){/* your implementation */}
}

MyClass my_object;


MyClass another_object(std::move(my_object));

It’s similar to the copy constructor, it accepts an instance of the same class as only parameter but it uses move semantics and it’s meant to fetch the resources of the instance given and leave it in an empty status.

There are another two special operations or rather operators. One is the copy assignment and the other the move assignment.

Copy assignment is meant to copy the data of an object in an existing object.

class MyClass
{
public:
	MyClass& operator = (const MyClass &) {/* your implementation */}
}

MyClass my_object,  another_object;

my_object = another_object;

Move assignment is like copy, it copies the data of an object in an existing object but it leaves the original object in an empty status.

class MyClass
{

public:
	MyClass& operator = (Class &&) {/* your implementation */}
}

MyClass my_object,  another_object;

my_object = std::move(another_object);

If C++ provides these 5 operations why would we need to have our own?

If you have an object with pointers and you don’t define a copy constructor C++ is going to provide the default copy constructor which only does shallow copies.

A shallow copy is that it will copy the pointers but not the buffers or objects those pointers point to, therefore the new and the old object point to the same resources, and when one of the two instances gets destroyed, most probably it’s going to release those resources and now the other object is going to be pointed unallocated memory.

In that case you need to define your copy constructor and manually copy those resources.

Also if your object holds resources, you need to tell C++ how to release them at the end of life of the object, otherwise they will remain locked and unaccessible and we don’t want that.

Based on the above discussion on what a copy constructor, move constructor, copy assignation, move assignation and destructor are, I’m going to explain the rule of 5.

The rule of 5 dictates that if you need to implement one of the 5 operations, you need to implement the 5 of them.

This is because if you need a destructor because you need to manually release resources, if you copy or move the object you also need to do something about.

Another option is to delete those operations so that you prevent an object from being copied or moved.

class MyClass
{
public:
	MyClass(int param1, int param2){/* your implementation */}

	MyClass(const MyClass& inst) = delete; // deletion of the copy constructor
	MyClass& operator =(const MyClass &) = delete; // deletion of the copy assignment
	MyClass(MyClass&& inst) = delete; // deletion of the move constructor
	MyClass& operator =(MyClass&&) = delete; // deletion of the move assignment
	
	~ MyClass() {/* your implementation */}
}

The point is that you don’t leave the default operation in effect but a customized one or deleted.

I hope it helps.

Leave a comment