3
votes

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:

  • make dtor pure virtual and define body for it
  • make dtor virtual and make ctor protected

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)?

2

2 Answers

6
votes

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.

1
votes

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.