1

Virtual destructors are needed when an object is (potentially) destructed from a base class pointer.

Consider a program without dynamic memory as often found in embedded systems. Here, using new or delete triggers a linker error, because the required underlying allocators are not implemented. Thus, the developers use only statically allocated objects (in bss/data section) or automatically allocated objects (usually on stack).

In such a system, is there any situation where a virtual destructor is truly needed? (Let's say nobody is bored and calls a destructor manually on some pointer.)

It seems to me that static and automatic allocation always invoke the correct destructor anyway. Do I miss anything? Are there any corner cases? What about static object pools in conjunction with unique_ptr and a custom deleter?

Alexander
  • 1,068
  • 6
  • 22
  • No, you did not missing anything. A *virtual* destructor would only be needed when invoking a derived destructor via a base class pointer. Otherwise, the base destructor does not need to be *virtual*. – Remy Lebeau Oct 08 '18 at 21:28
  • As a matter of course, make all your classes which you are not designing to be polymorphic to be `final`. Your future self will thank you. – Eljay Oct 08 '18 at 21:32
  • 2
    Could be of some use for releasing resources other than memory. It's also not uncommon to use custom placement new/delete. OTOH, templates are generally better for polymorphic work these days. – doug Oct 08 '18 at 21:32
  • "(Let's say nobody is bored and calls a destructor manually on some pointer." - what does that mean? –  Oct 08 '18 at 21:34
  • Expanding on what Doug said, templates for static polymorphism (i.e., polymorphism at compile time) can accomplish much of which dynamic polymorphism is over-used for. – Eljay Oct 08 '18 at 21:34
  • @NeilButterworth Meaning nobody casts a pointer to the base class of a stack allocated object and manually calls `base->~Base()`. – Alexander Oct 08 '18 at 21:37
  • @Eljay: "*As a matter of course, make all your classes which you are not designing to be polymorphic to be final. Your future self will thank you.*" No, your future-self will hate you when they make an allocator type `final` and find that standard library types want to inherit from it (for EBO reasons) but *cannot* and thus break. Making a class `final` by default is extremely rude in C++. We use inheritance for a lot more than just polymorphism. – Nicol Bolas Oct 08 '18 at 22:04
  • @NicolBolas • Not in my experience. Classes not designed to be used polymorphically cause a multitude of problems. Why anyone would expect EBO from a non-polymorphic class I cannot fathom. – Eljay Oct 08 '18 at 22:31
  • @Eljay: "*Why anyone would expect EBO from a non-polymorphic class I cannot fathom.*" Consider `std::vector>`. The allocator has no members, so it doesn't need to take up space. But if you make it a member subobject of the `vector`, it *has to* take up space. So instead, it inherits from `allocator`; base classes don't *have to* take up space in the derived class. This makes `vector` smaller; it only takes up 3 pointers of space. This is a fairly common tool for low-level service code of this sort. – Nicol Bolas Oct 08 '18 at 22:36
  • @Eljay: That's fine. But I gave you an example from pretty much every implementation of *the C++ standard library*. Advice that cannot even be employed with the standard library for the language they're working with is perhaps not the best advice. I'm not saying that your use cases are wrong or anything. It's just that perhaps your perspective is not wide enough if your advice breaks when applied to standard libraries. – Nicol Bolas Oct 08 '18 at 22:44
  • @NicolBolas • EBO is not applicable to most non-polymorphic classes. – Eljay Oct 08 '18 at 22:46
  • @Eljay: EBO is a matter of the *user* of a type. EBO in the case of `vector` is caused by the *`vector`* implementation, not the `allocator` class. An empty `allocator` class merely is able to make efficient use of `vector`. It doesn't matter what you feel is applicable or not, a `final` allocator may break standard library containers. – Nicol Bolas Oct 08 '18 at 22:49
  • @NicolBolas • Okay, I have not needed to make an allocator in a long time. My apologies. Don't make an allocator final. For non-allocator, non-polymorphic classes, make them final. – Eljay Oct 08 '18 at 23:12
  • @Eljay: Unless of course someone wants to use your type in [a certain kind of mixin](https://stackoverflow.com/q/26486456/734069), which requires inheriting from it. And since the point of a mixin is that the writer of the class doesn't actually know that a user will want to use it in that mixin, you can't know for sure if your type will be used in that way or not. And there are plenty of other idioms in C++ that break on class-`final`. Class-`final` is not something you should use *thoughtlessly* in C++. – Nicol Bolas Oct 08 '18 at 23:15
  • @NicolBolas • Class `final` is something that should be omitted _thoughtfully_. It should be omitted for mixins which have no data members, which are designed to be used polymorphically. It should be omitted for classes designed to be a polymorphic base class. It should be removed if a class needs to be made into a polymorphic base, and at that time the class is reworked to be suitable as a polymorphic base class. Although too late, C++ should have had `final` be the default and opt-in to dynamic polymorphism. – Eljay Oct 09 '18 at 11:32

1 Answers1

4

Let's say nobody is bored and calls a destructor manually on some pointer.

I think you've dismissed this possibility too quickly. Embedded/memory limited systems where dynamic allocation is forbidden still can create objects with dynamic storage duration. Observe:

alignas(T) char memory[sizeof(T)];
T *p = new(memory) T;  //Does not call global `new` allocator.

/*do stuff with `p`*/

p->~T();

There is no reason to forbid this. Indeed, some implementations of type erasure rely on this with small object optimization. std::any implementations for small objects can construct the derived class entirely using memory in the std::any object itself. But it still needs to call the destructor of the type, typically though a base class pointer. There are implementations of any that don't use inheritance of course, but my overall point is that it would be weird to expressly forbid calling a destructor manually.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982