When organizing inheritance structure and needing to make base class abstract, is there any reason to prefer a pure virtual destructor over protected constructor (or vice versa)? This question already exists here, but the accepted answer doesn't really answers it. The problem of
Base *ptr = new Derived();
delete ptr;
can be solved using both approaches:
As I understand, the only thing that will differ is the compiler error at trying to Base *ptr = new Base(): Cannot instantiate abstract class in first case vs. Cannot access protected member in second case, with first being more accurate. Is this the question of style, or is there something I'm not aware of?
As a somewhat related question, will the same rules be applied to some class in inheritance chain that is not strictly a base one, but still should be abstract (i.e. Monster inheriting from GameObject cannot be created, but Orc inheriting from Monster can)?
If only the default-constructor is protected, you can still create an instance using the implicitly-defined copy-constructor:
struct ProtectedBase
{
virtual ~ProtectedBase() = default;
protected:
ProtectedBase(){}
};
struct ProtectedDerived : ProtectedBase {};
struct VirtualBase {
virtual ~VirtualBase() = 0;
};
VirtualBase::~VirtualBase() = default;
struct VirtualDerived : VirtualBase {};
int main() {
ProtectedBase a { ProtectedDerived{} }; //valid
VirtualBase b { VirtualDerived{} }; //invalid
}
So you'd need to explicitly mark all constructors as protected, including any which are implicitly-defined. This seems like more work than simply making a pure-virtual destructor and implementing it. It's also a lot easier to miss something out by mistake.
At a more conceptual level, if you are trying to make a class abstract, then make it abstract. Implementing something which makes it look like an abstract class so long as you don't stare for too long is not very helpful IMHO.
If you have a non-abstract class with a protected constructor, you can still undesirably instantiate the class within a method of a derived class, since it will have access to protected members of the base class:
// Devious code to bypass protection
class Devious : public ProtectedBase {
public:
ProtectedBase *createBase() const {
return new ProtectedBase();
}
}
By making the class abstract, you protect against such possibilities. To make the class abstract, you don't need to make the destructor pure virtual, as long as at least one member function is pure virtual. It is just a convention that the destructor is made pure virtual if there is no other member function that is reasonable to make pure virtual.
In answer to your second question, it doesn't matter whether the base is at the top of the hierarchy or inherits from some other base.
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