Rule of three corresponds to automatic generation of 3 special member functions – constructor, destructor and assignment by C++ compiler.
In C++11, we know that if a class designer declares any one of the special member functions, then it means that there is something special about the way in which these special operations need to be done. Therefore, the class designer has to define all three and compiler will not generate other functions.
The compiler will not generate move operation, when
- The user provides a move constructor
- Or if user provides any of constructor or copy constructor
- If user provides a destructor
- Lastly user provides the assignment operator
In following example, move constructor of Base class will be called from Derived class
#include <iostream> //main header
using namespace std; //namespace
class BaseC
{
public:
BaseC()
{
cout << "Base Constructor"
<< endl;
}
BaseC(const Base& rhs)
{
cout << "Base Copy Constructor"
<< endl;
}
BaseC(BaseC&& rhs)
{
cout << "Base Move Constructor"
<< endl;
}
};
class DerivedC : public BaseC
{
public:
DerivedC()
{
cout << "Derived Constructor"
<< endl;
}
DerivedC(DerivedC&& rhs) : BaseC(std::move(rhs))
{
cout << "Derived Move Constructor"
<< endl;
}
};
int main()
{
DerivedC d1;
DerivedC d2 = std::move(d1);
return 0;
}
The output is:
Base Constructor
Derived Constructor
Base Move Constructor
Derived Move Constructor
Now, in same example, consider that the class decides to delete user-defined move constructor in Base class. Now the compiler will not going to generate move constructor and eventually will call a copy constructor.
#include <iostream> //main header
using namespace std; //namespace
class BaseC
{
public:
BaseC()
{
cout << "Base Constructor"
<< endl;
}
BaseC(const BaseC& rhs) //This will be called now
{
cout << "Base Copy Constructor"
<< endl;
}
// BaseC(BaseC&& rhs)
//Move Constructor is Commented
// {
// cout << "Base Move Constructor"
// << endl;
// }
};
class DerivedC : public BaseC
{
public:
DerivedC()
{
cout << "Derived Constructor"
<< endl;
}
DerivedC(DerivedC&& rhs) : BaseC(std::move(rhs))
{
cout << "Derived Move Constructor"
<< endl;
}
};
int main()
{
DerivedC d1;
DerivedC d2 = std::move(d1);
return 0;
}
The output is:
Base Constructor
Derived Constructor
Base Copy Constructor
Derived Move Constructor
Compatibility with old C++.
Another consequence of the Rule of Three is explained. When a class declares a destructor, then in ideal case, the compiler should not generate both the copy as well as the move operations. This is because they wouldn’t do the semantically correct operation.
However, it is too late to apply this rule now in old C++ and applying this rule now in old C++ would break the compatibility with Legacy code. Earlier, old C++ (C++98) did not applied this rule of three properly and therefore, they still generate functions in contradictions with rule of three.
C++11 has deprecated the automatic generation of copy & assignment for classes declaring copy operations or a destructor