3

I work within an environment where I know that all child classes of an abstract base class will never be deleted via a pointer to this abstract base class. So I don't see the need to have this base class to provide with an virtual destructor. Therefore I make the destructor protected, which seems to do what I want.

Here is a simplified example of this:

#include <iostream>

struct Base
{
    virtual void x() = 0;
protected:
    ~Base() =default;
};

struct Child: Base
{
    void x() override
    {
        std::cout << "Child\n";
    }
};

int main()
{
    // new and delete are here to make a simple example,
    // the platform does not provide them
    Child *c = new Child{};
    Base *b=c;

    b->x();
    // delete b; // does not compile, as requested
    delete c;
    return 0;
}

Is it sufficient to make the destructor protected to be save against unwanted base class deletions, or do I miss something important here?

Rudi
  • 19,366
  • 3
  • 55
  • 77
  • 4
    Given that your code dynamically creates and destroys the object, can you explain *why* you don't want a "base class deletion" to work? Apart from a diagnostic on `delete b` in the code you have shown, what does such a thing allow you to achieve? Avoiding undefined behaviour due to lack of a virtual destructor doesn't count - since your class is abstract, having a virtual destructor costs nothing. – Peter Feb 21 '20 at 08:45
  • `Child *c = new Child{};` should be `Child *c = new Child();` no ? – Landstalker Feb 21 '20 at 08:48
  • 1
    At a very first glance, this seems to look safe. But if children might be inherited from, then you might have to re-introduce the virtual destructor from within these anyway – or get into danger again. So better to have a virtual destructor right from the start already in `Base`. How many deletions will you have? I cannot imagine that bit of overhead for a virtual function call being worth the loss of safety... – Aconcagua Feb 21 '20 at 08:49
  • @Landstalker Uniform object initialisation since C++11... I consider it flawed for a bunch of design errors and recommend to avoid it whenever possible, but at least it's valid C++... – Aconcagua Feb 21 '20 at 08:51
  • Does this answer your question? [Should every class have a virtual destructor?](https://stackoverflow.com/questions/353817/should-every-class-have-a-virtual-destructor) and answer says clearly that the destructor could be protected – Jaffa Feb 21 '20 at 08:52
  • 2
    What makes you think that a derived class will not be deleted via a pointer to its abstract base ? This is exactly what virtual destructors are meant to. [Both gcc and clang](https://godbolt.org/z/VtnhDD) prove that it works fine (unless I have misunderstood the question). – Fareanor Feb 21 '20 at 08:53
  • 2
    @Geoffroy Closely related, but not exacly a duplicate. Question asks if we should generally introduce virtual destructors even in classes otherwise not being virtual – whereas this question wants to avoid a virtual destructor even though the class already *has* other virtual functions... – Aconcagua Feb 21 '20 at 08:56
  • @Peter @Fareanor This is an overly simplified example, the real environment is an embedded system where new and delete are not available, and the memory management is manually done. I put the new and delete calls in here to have an example where a random `delete b` is not entirely alien. – Rudi Feb 21 '20 at 09:48
  • @Rudi Well, in *that* scenario (µC) even more: *Do* make the destructor of base virtual! In case of ` { Child c; /* do something with c */ }`, there's no pointer involved anyway, thus there won't be a virtual destructor call at all even though it was virtual in base class, as the concrete type is known at compile time already! – Aconcagua Feb 21 '20 at 10:06
  • @Rudi Oh, thanks for the clarification, I missed the point then. – Fareanor Feb 21 '20 at 10:06

1 Answers1

3

Is it a good idea to [...]

From a safety point of view I answer with a clear 'no':

Consider the case that the child class is inherited again – maybe someone else than you does that. That person might overlook that you violated good practice in Base and assume that the destructor of Child already is virtual – and delete GrandChild via pointer to Child...

To avoid that situation, you could

  • make the destructor virtual in Child again – so in the end, nothing gained anyway.
  • or declare Child final, imposing quite some limits that most likely are meaningless from any other point of view.

And you'd have to opt for one of these for any derived class. All for avoiding a single virtual function call on object deletion?

How often would you do that at all? If frequency of object deletion really is an issue, then consider the overhead of allocating and freeing the memory again. In such a case, it's most likely more efficient to allocate memory just once (sufficently large and appropriately aligned to hold any of the objects you consider to create), do placement new and explicit destructor calls instead and finally free the memory again just once when you are done completely and don't need it any more. That will compensate the virtual function call by much...

Aconcagua
  • 24,880
  • 4
  • 34
  • 59