The Rule of Three in C++ states that if a class defines any of the following three functions, it should define all three:
Copy constructor
Copy assignment operator
Destructor
The reason for this is that these functions are all related to memory management and ownership of resources, and if any one of them is implemented incorrectly, it can lead to bugs, crashes, and memory leaks.
The copy constructor is called when a new object is created from an existing object of the same class. It creates a new object with the same values as the existing object. If the copy constructor is not defined, the compiler will provide a default implementation that simply copies the values of the data members. However, if the class owns any resources, such as dynamic memory or file handles, this default implementation may not be sufficient.
The copy assignment operator is called when an existing object is assigned to another existing object of the same class. It copies the values of the data members from the right-hand side object to the left-hand side object. If the copy assignment operator is not defined, the compiler will provide a default implementation that simply copies the values of the data members. However, like the copy constructor, this default implementation may not be sufficient if the class owns any resources.
The destructor is called when an object is destroyed, either because it goes out of scope or because it is deleted with the delete keyword. The destructor is responsible for cleaning up any resources that the object owns, such as dynamic memory or file handles. If the destructor is not defined, the compiler will provide a default implementation that simply destroys the object without cleaning up any resources it may own.
Here’s an example of a class that violates the Rule of Three:
#include <iostream>
#include <cstring>
using namespace std;
class Person {
public:
Person(const char* name) {
this->name = new char[strlen(name) + 1];
strcpy(this->name, name);
}
~Person() {
delete[] name;
}
void setName(const char* name) {
delete[] this->name;
this->name = new char[strlen(name) + 1];
strcpy(this->name, name);
}
void printName() {
cout << name << endl;
}
private:
char* name;
};
int main() {
Person p1("John");
Person p2 = p1;
p1.printName();
p2.printName();
p2.setName("Jane");
p1.printName();
p2.printName();
return 0;
}
In this example, we define a class called Person that represents a person with a name. The class has a single data member, name, which is a dynamically allocated C-style string. We define a constructor to initialize the name, a destructor to clean up the name when the object is destroyed, and a setName() function to change the name.
In the main() function, we create two Person objects, p1 and p2, with the same name "John". We then modify the name of p2 to "Jane" using the setName() function. However, since the class does not define a copy constructor or a copy assignment operator, when p2 is created as a copy of p1, it simply copies the memory address of the name member. This means that both objects end up sharing the same memory for the name member, and when the name is changed for one object, it is changed for the other object as well.
To fix this issue and adhere to the Rule of Three, we would need to define a copy constructor and a copy assignment operator for the Person class that create new memory for the name member and copy the contents of the original object’s name member.