This rule basically states that if a class defines one (or more) of the following, it should explicitly define all three, which are:
Now let us try to understand why?
The default constructors and assignment operators do shallow copy and we create our own constructor and assignment operators when we need to perform a deep copy (For example when a class contains pointers pointing to dynamically allocated resources).
First, what does a destructor do? It contains code that runs whenever an object is destroyed. Only affecting the contents of the object would be useless. An object in the process of being destroyed cannot have any changes made to it. Therefore, the destructor affects the program's state as a whole.
Now, suppose our class does not have a copy constructor. Copying an object will copy all of its data members to the target object. In this case when the object is destroyed the destructor runs twice. Also the destructor has the same information for each object being destroyed. In the absence of an appropriately defined copy constructor, the destructor is executed twice when it should only execute once. This duplicate execution is a source for trouble.
A coding example follows:
C++
// In the below C++ code, we have created
// a destructor, but no copy constructor
// and no copy assignment operator.
class Array
{
private:
int size;
int* vals;
public:
~Array();
Array( int s, int* v );
};
Array::~Array()
{
delete vals;
vals = NULL;
}
Array::Array( int s, int* v )
{
size = s;
vals = new int[ size ];
std::copy( v, v + size, vals );
}
int main()
{
int vals[ 4 ] = { 11, 22, 33, 44 };
Array a1( 4, vals );
// This line causes problems.
Array a2( a1 );
return 0;
}
In the example above, once the program goes out of scope, the class destructor is called, not once but twice. First due to deletion of a1 and then of a2. The default copy constructor makes a copy of the pointer vals and does not allocate memory for it. Thus, on deletion of a1, the destructor frees vals. All subsequent vals containing instances when trying to be deleted by the destructor causes the program to crash, as vals do not exist anymore.
This is similar in the case of copy assignment operator. If a class does not have an explicitly defined assignment operator, implicit assignment of all source's data members to the target's corresponding data members will occur. All in all, it creates a copy, which again is the same problem defined previously.
References:
Advantages of the Rule of Three
1. Ensuring correct resource management particularly dynamic memory allocated during an object's lifespan
2. In order to avoid memory leakage releasing resources through a well-defined destructor when an object is no longer needed.
3. copy constructor and copy assignment operator are used to manage object copies, which makes sure to prevent any accidental sharing of resources
4. Using “The rule of three” makes it really clear which objects are accountable for releasing particular resources, i.e. enforcing clear ownership semantics.
5. the Rule of Three, allowing seamless integration with standard library functionalities for the stored objects that follow this.
6. Clearly outlining copy-related tasks minimizes the chance of unwilling errors, by identifying any possible mistakes during the compilation process
7. This rule encourages code that is simple to comprehend and maintain by explicitly managing ownership and resources
8. By making use of this rule, C++ code becomes more reliable, decreasing the chances of encountering subtle bugs that could cause issues with resource management.
9. For codebases that haven't upgraded to C++11 or later, it's still essential to adhere to the Rule of Three as a key guideline for maintaining code stability.
10. Before diving into the benefits of move semantics and the Rule of Five, it is important to know about this rule of three
Disadvantages of the Rule of Three
1. You'll need to add some extra lines of code for the copy constructor, copy assignment operator, and destructor when implementing the Rule of Three.
2. Due to dealing with multiple resources or intricate ownership scenarios can make a class more complex.
3. By following the Rule of Three too strictly might restrict the creativity of certain classes, particularly when you require more advanced features like move semantics.
4. In some cases, a class may not have to handle resources or execute custom copy operations, which renders this Rule.
5. The Rule of Three can be a problem in situations where classes need to work with C libraries or interfaces that don't have copy-related functions.
6. The Rule of Three can be quite a challenge for beginners, as it requires a deep understanding and precise implementation. It can result in an error if not done correctly.
Conclusion
The Rule of Three in C++ is a reasonable principle that supports us in managing numerous resources and ensuring the correct behaviour of objects during copies and destruction. Although it offers advantages in terms of resource administration i.e. management and code protection, it also brings along some downsides like added complexity and the possibility of redundancy. As C++ continues to evolve, developers should also consider the Rule of Five and move semantics to perform more efficient and expressive code. Having a deep understanding of these rules is essential for creating robust, maintainable, and efficient C++ code.
Similar Reads
The Rule of Five in C++
The "Rule of Five" is a guideline for efficient and bug-free programming in C++. The Rule of Five states that, If any of the below functions is defined for a class, then it is better to define all of them. It includes the following functions of a class: DestructorCopy ConstructorCopy Assignment Oper
6 min read
Structures in C++
C++ Structures are used to create user defined data types which are used to store group of items of different data types.SyntaxBefore using structure, we have to first define the structure using the struct keyword as shown:C++struct name{ type1 mem1; type2 mem2; ... };where structure name is name an
8 min read
Swap three numbers in cycle
Given three numbers, swap them in cyclic form. First number should get the value of third, second should get the value of first and third should get value of second. Examples: Input : a = 2, b = 4, c = 7 Output : a = 7, b = 2, c = 4 Input : a = 10, b = 20, c = 30 Output : a = 30, b = 10, c = 20 Prer
5 min read
Operators in C++
C++ operators are the symbols that operate on values to perform specific mathematical or logical computations on given values. They are the foundation of any programming language.Example:C++#include <iostream> using namespace std; int main() { int a = 10 + 20; cout << a; return 0; }Outpu
9 min read
Map of Tuples in C++ with Examples
What is a tuple? A tuple in C++ is an object that has the ability to group a number of elements. The elements can be of the same type as well as different data types. The order in which tuple elements are initialized can be accessed in the same order. Functions associated with a tuple: 1. make_tuple
4 min read
logical_or in C++
logical_or in C++ is a binary function object class which returns the result of the logical "or" operation between its two arguments (as returned by operator ||). Syntax: template struct logical_or : binary_function { T operator() (const T& a, const T& b) const {return a||b;} }; Parameters:
2 min read
Tuples in C++
A tuple is an object that can hold a number of elements. The elements can be of different data types. The elements of tuples are initialized as arguments in order in which they will be accessed. Tuples are a versatile data structure for grouping values. To understand how to use tuples in C++ and the
5 min read
rotate() in C++ STL
In C++, rotate() is a built-in function used to rotate the elements of a range in left or right direction such that the element pointed to by a specified iterator becomes the new first element of the range.Let's take a look at an example:C++#include <bits/stdc++.h> using namespace std; int mai
4 min read
Deque of Tuples in C++ with Examples
What is deque? In C++, a deque is a sequence container and it is also known by the name, double-ended queue. As the name implies, a deque allows insertion and deletion from both ends. Although a deque is similar to a vector, deques are more efficient compared to vectors. In vectors, contiguous stora
8 min read
islessgreater() in C/C++
In C++, islessgreater() is a predefined function used for mathematical calculations. math.h is the header file required for various mathematical functions.islessgreater() function is used to check whether the 1st argument given to the function is less than or greater than the 2nd argument given to t
3 min read