What is the correct way to declare instantiation methods when defining an interface class?
Abstract base classes are required to have a virtual destructor for obvious reasons. However, the following compilation warning is then given: "'InterfaceClass' defines a non-default destructor but does not define a copy constructor, a copy assignment operator, a move constructor or a move assignment operator", which is the 'rule of five'.
I understand why the 'rule of five' should be obeyed in general, but is it still applicable for an abstract base class or interface?
My implimentation is then:
class InterfaceClass
{
// == INSTANTIATION ==
protected:
// -- Constructors --
InterfaceClass() = default;
InterfaceClass(const InterfaceClass&) = default;
InterfaceClass(InterfaceClass&&) = default;
public:
// -- Destructors --
virtual ~InterfaceClass() = 0;
// == OPERATORS ==
protected:
// -- Assignment --
InterfaceClass& operator=(const InterfaceClass&) = default;
InterfaceClass& operator=(InterfaceClass&&) = default;
// == METHODS ==
public:
// Some pure interface methods here...
};
// == INSTANTIATION ==
// -- Destructors --
InterfaceClass::~InterfaceClass()
{
}
Is this correct? Should these methods be = delete
instead? Is there some way of declaring the destructor to be virtual pure whilst also somehow remaining default?
Even if I declare the destructor as: virtual ~InterfaceClass() = default;
, if I do not explicitly default the other four then I will get the same compiler warning.
Tl;dr: What is the correct way to satisfy the 'rule of five' for an interface class as the user must define a virtual destructor.
Thanks for your time and help!
The Rule of Five is a programming concept brought about in C++11. It originates from the Rule of Three, where the introduction of Move Semantics in C++11, caused the Rule Of Three to expand and become the Rule Of Five. Before we talk about the Rule of Five however, we will briefly talk about the Rule Of Three to understand it’s base.
Firstly, the rule of three specifies that if a class implements any of the following functions, it should implement all of them: 1 copy constructor 2 copy assignment operator 3 destructor More ...
This post is about the rule of zero, five, or maybe six. I will also show the difference between copy and reference semantic and a quite similar topic: deep versus shallow copy. To be precise, C++ has about 50 rules for managing the lifecycle of an object. This time I will write about the three very important default operation rules.
The Rule of Five is a modern extension to the Rule of Three. The Rule of Five states that if a type ever needs one of the following, then it must have all five. In addition to copy semantics (Rule of Three), we also have to implement move semantics.
Is this correct? Should these methods be = delete instead?
Your code seems correct. The need of defining special copy/move member functions as default and protected comes clear when you try to copy a derived class polymorphycally. Consider this additional code:
#include <iostream>
class ImplementationClass : public InterfaceClass
{
private:
int data;
public:
ImplementationClass()
{
data=0;
};
ImplementationClass(int p_data)
{
data=p_data;
};
void print()
{
std::cout<<data<<std::endl;
};
};
int main()
{
ImplementationClass A{1};
ImplementationClass B{2};
InterfaceClass *A_p = &A;
InterfaceClass *B_p = &B;
// polymorphic copy
*B_p=*A_p;
B.print();
// regular copy
B=A;
B.print();
return 0;
}
And consider 4 options for defining special copy/move member functions in your InterfaceClass.
With special copy/move member functions deleted in your InterfaceClass, you would prevent polymorphic copy:
*B_p = *A_p; // would not compile, copy is deleted in InterfaceClass
This is good, because polymorphic copy would not be able to copy the data member in the derived class.
On the other hand, you would also prevent normal copy, as the compiler won't be able to implicitly generate a copy assignment operator without the base class copy assignment operator:
B = A; // would not compile either, copy assignment is deleted in ImplementationClass
With copy/move special member functions as default and public, (or without defining copy/move member functions), normal copy would work:
B = A; //will compile and work correctly
but polymorphic copy would be enabled and lead to slicing:
*B_p = *A_p; // will compile but will not copy the extra data members in the derived class.
If move© special member functions are not defined, behavior with respect to copy is similar to 2: the compiler will implicitly generate deprecated copy special members (leading to polymorphic slicing). However in this case the compiler will not implicitly generate move special members, so copy will be used where a move would be possible.
With special copy/move member functions as default and protected, as in your example, you will prevent polymorphic copy which would otherwise had lead to slicing:
*B_p = *A_p; // will not compile, copy is protected in InterfaceClass
However, the compiler will explicitly generate a default copy assignment operator for InterfaceClass, and ImplementationClass will be able to implicitly generate its copy assignment operator:
B = A; //will compile and work correctly
So your approach seems the best and safest alternative
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With