The Rule of Five is a C++ guideline that states that if a class defines one of the special member functions responsible for resource management, it should usually define all five. This ensures proper copying, moving, and destruction of objects that manage resources such as dynamic memory, files, or sockets.
- Helps manage dynamically allocated resources safely.
- Prevents memory leaks, double deletion, and shallow-copy issues.
The Five Special Member Functions
The Rule of Five consists of the following five member functions.
1. Destructor
The Destructor is used for removing/freeing up all the resources that an object has taken throughout its lifetime.With the help of this destructor, we make sure that any resources taken by objects are properly released before the object is no longer in scope.
Syntax
class ClassName {
public:
~ClassName() {
// Release allocated resources
}
};
2. Copy Constructor
Copy Constructor is used to make a new object by copying an existing object. Copy constructor is invoked when we use it to pass an object by value or when we make a copy explicitly. mainly we use copy constructor to replicate an already existing object.
Syntax
class ClassName {
public:
ClassName(const ClassName& other) {
// Deep copy resources
}
};
3. Copy Assignment Operator
Copy Assignment Operator is a special type of function that takes care of assigning the data of one object to another object. It gets called when you use this assignment operator (=) between objects.
Syntax
class ClassName {
public:
ClassName& operator=(const ClassName& other) {
if (this != &other) {
// Deep copy resources
}
return *this;
}
};
4. Move Constructor
Move Constructor is one of the member functions that is used to transfer the ownership of resources from one object to another. This job can easily be done by this move constructor by using a temporary object called rvalue
Syntax
class ClassName {
public:
ClassName(ClassName&& other) noexcept {
// Transfer ownership
}
};
Note: The noexcept specifier indicates that the move constructor does not throw exceptions, allowing standard containers to use move operations more efficiently.
5. Move Assignment Operator
The Move Assignment Operator is comparable to the Move Constructor. It is used when an existing object is assigned the value of an rvalue. It is activated when you use the assignment operator (=) to assign the data of a temporary object(value) to an existing object.
Syntax
class ClassName {
public:
ClassName& operator=(ClassName&& other) noexcept {
if (this != &other) {
// Transfer ownership
}
return *this;
}
};
Example: Program to demonstrates all five special member functions working together.
#include <iostream>
#include <utility> // for using move
using namespace std;
class ResourceManager {
private:
int* data;
size_t size;
public:
// default constructor
ResourceManager(size_t sz = 0)
: data(new int[sz])
, size(sz)
{
cout << "Default Constructor is called" << endl;
}
// Destructor
~ResourceManager()
{
delete[] data;
cout << "Destructor is called" << endl;
}
// Copy Constructor
ResourceManager(const ResourceManager& other)
: data(new int[other.size])
, size(other.size)
{
copy(other.data, other.data + other.size, data);
cout << "Copy Constructor is called" << endl;
}
// Copy Assignment Operator
ResourceManager& operator=(const ResourceManager& other)
{
if (this != &other) {
delete[] data;
data = new int[other.size];
size = other.size;
copy(other.data, other.data + other.size, data);
}
cout << "Copy Assignment Operator is called"
<< endl;
return *this;
}
// Move Constructor
ResourceManager(ResourceManager&& other) noexcept
: data(other.data),
size(other.size)
{
other.data = nullptr;
other.size = 0;
cout << "Move Constructor" << endl;
}
// Move Assignment Operator
ResourceManager&
operator=(ResourceManager&& other) noexcept
{
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
cout << "Move Assignment Operator" << endl;
return *this;
}
};
int main()
{
// Creating an object using default constructor
ResourceManager obj1(5);
// Creating an object using Copy constructor
ResourceManager obj2 = obj1;
// Creating an object using Copy assignment operator
ResourceManager obj3;
obj3 = obj1;
// Creating an object using Move constructor
ResourceManager obj4 = move(obj1);
// Creating an object using Move assignment operator
ResourceManager obj5;
obj5 = move(obj2);
return 0;
}
Output
Default Constructor is called
Copy Constructor is called
Default Constructor is called
Copy Assignment Operator is called
Move Constructor
Default Constructor is called
Move Assignment Operator
Destructor is called
Destructor is called
Destructor is called
Destructor is called
Destructor is called
Explanation
- The default constructor allocates the resource and copy operations create deep copies of the resource.
- Move operations transfer resource ownership efficiently and the destructor releases the allocated memory automatically.
Advantages of the Rule of Five
The Rule of Five offers several benefits when working with resource-owning classes.
- Prevents memory leaks and double deletion.
- Ensures correct ownership transfer during copy and move operations.
- Improves performance by avoiding unnecessary deep copies.
- Makes resource management safer and more predictable.
Best Practices
Follow these practices when implementing the Rule of Five.
- Implement all five special member functions for classes that own resources.
- Prefer move operations whenever ownership can be transferred.
- Use noexcept for move constructor and move assignment operator.
- Consider using smart pointers to avoid manual resource management whenever possible.
Rule of Three vs Rule of Five
The Rule of Five extends the older Rule of Three by adding support for move semantics introduced in C++11.
| Rule of Three | Rule of Five |
|---|---|
| Destructor | Destructor |
| Copy Constructor | Copy Constructor |
| Copy Assignment Operator | Copy Assignment Operator |
| — | Move Constructor |
| — | Move Assignment Operator |