3

When designing a pure virtual base class (interface) in C++, in what cases would I not want to allow polymorphic deletion via a pointer to my base class?

I've seen questions about why you should make the destructor virtual or not (e.g. this one). They often include guidance that says you should make the destructor public and virtual or protected and non-virtual. However, if I don't know how users will derive from my interface or how they will want to delete objects of the derived class, why wouldn't I always make the destructor virtual in a pure virtual base class?

2
  • @DocBrown, I understand that defaulting to virtual allows flexibility. My question is, in what specific cases would I not want to allow that flexibility when defining a pure virtual base class (interface - i.e. no member data or non-virtual functions)? I'm not seeing a use case where I would want to specifically prohibit that behavior by making the destructor protected and non-virtual. Commented Mar 4 at 16:57
  • After reading your question twice, I guess your trouble is mainly this answer, which says "use virtual destructors when you need polymorhic deletion". Now you are asking "don't I have to expect the need for polymorhic deletion for each inheritable class"? Commented Mar 5 at 7:19

4 Answers 4

2

Let us precisely look at what you wrote:

If I don't know how users will derive from my interface or how they will want to delete objects of the derived class, why wouldn't I always make the destructor virtual in a pure virtual base class?

If you really don't know how users of a certain class will delete the objects, you will better off to design a class for polymorphic deletion, hence add a virtual base class destructor "just in case". That's explained in the top answer of the question you already mentioned:

.. [having a virtual destructor is] not always necessary, but it ensures the classes in the hierarchy can be used more freely without accidental undefined behaviour if a derived class destructor is invoked using a base class pointer or reference.

However, as a library designer, you may design a library with a virtual class/interface with a more specific contract in mind how it has to be used. You may decide that

  • construction and deletion of concrete instances are completely the responsibility of the user of a lib, and expected to happen in a context where the concrete types are known (hence no polymorphic deletion)

  • the library itself only implements means like the template method pattern, but no deletion of the objects.

For such a case, a pure virtual destructor is probably superfluous and hence negligible, in which case one should make the destructor non-virtual and protected.

For example, think of a program designed with dependency-injection in mind, where several modules are implemented as a class with an interface, for making it possible to mock the class out during unit testing. For these modules, one usually creates only one instance of the class during the live time of the program, and the deletion only happens when the program ends. Why would you need a virtual destructor for such a class? It is perfectly possible to omit any explicit deletion for such a class and rely on the operating system to clean up the processes memory when the program ends.

For a more in-depth discussion about when and how to use virtual destructors, and when not, see Herb Sutter's article from the C++ user's journal from 2001. The article shows also an example of struct unary_function from the old standard libs, which is inheritable, still a virtual destructor isn't needed.

5
  • Protected destructor on an public non-final class gives dangerous illusion of safety. stackoverflow.com/a/3246823/125562 Commented Mar 5 at 9:50
  • @Basilevs: feel free to suggest a better solution for communicating that polymorphic deletion shall not happen for an abstract virtual class. Commented Mar 5 at 10:33
  • "construction and deletion of concrete instances are completely the responsibility of the user of a lib" nearly addresses my question. However, I still don't understand why "expected to happen in a context where the concrete types are known" is a constraint I would ever want to impose on the user of a base class containing only pure virtual methods (no data, no implementation), I'm coming to the conclusion that the "or protected and non-virtual" part of that guidance was not meant to apply to the constraints I put on the question. Commented Mar 5 at 21:51
  • @ToddR: I guess instead of theorizing, it would help your understanding to look at more real-world examples. Commented Mar 6 at 6:28
  • @DocBrown I promise I'm not trying to be difficult :). My original question stemmed from exactly that. I was trying to understand if there is a real-world use case, since I can't think of one. Commented Mar 6 at 10:54
1

Unlike other methods, the class of an instance changes while you are running a chain of constructors or destructors. So if a virtual method is called from destructor or constructor it might not be the one you expect, and you need to be extra careful.

1
  • 2
    Virtual dispatch during con-/de-struction doesn't really matter to whether polymorphic deletion should be allowed. Commented Mar 4 at 15:17
1

The C++ core guidelines indeed recommends

A base class destructor should be either public and virtual, or protected and non-virtual

The reason is that

  1. derived classes may need their own destructor, for example if they have additional non trivial attributes;
  2. the compiler will not know which destructor to call for dynamically created polymorphic objets, and the way to help the compiler to get the right one, is to make the destructor virtual.

The following code (online demo) shows how non-virtual public destructors can screw up, failing to properly destroy the derived class (which means potential leakages, locked resources, etc..):

struct A { // Class to trace what happens with attributes of the derived class A() { cout<<" Create attribute class"<<endl; } ~A() { cout<<" Destroy attribute class"<<endl; } }; class X { // Base class public: X() { cout<<" Create base class"<<endl; } virtual void test() { cout<<" Test base class"<<endl; } ~X() { cout<<" Destroy base class"<<endl; } // oops }; class XX : public X { // Derived class A a; public: XX() { cout<<" Create derived class"<<endl; } void test() override { cout<<" Test derived class"<<endl; } ~XX() { cout<<" Destroy derived class"<<endl; } }; void kill_it (X* t) { t->test(); delete t; } int main() { X *poly; cout<<"1) Test with base class:"<<endl; poly = new X(); kill_it (poly); cout<<endl<<"2) Test with derived class"<<endl; poly = new XX(); kill_it (poly); cout<<endl<<"3) How it should be with derived class"<<endl; { XX dummy_destroyed_when_leaving_the_block; } } 

For the sake of completeness, you'll easily find out that just making the destructor virtual makes it work correctly (other online demo).

This problem arises also if your design uses smart pointers, unless you think to explicitly provide a deleter for each and every unique_ptr and shared_ptr constructors, which is much more cumbersome and error prone.

You may want to keep the destructor public and non-virtual only in limited situations, for example:

  • your class only encapsulatse related functions without any attributes (seems to be the case with your interface),
  • when no attributes are added to the derived class,
  • no RAII is performed
  • object destruction of derived classes will never ever happen via a pointer to the base class.

But still, it'll remain a maintenance risk (if you need to edit your code in 3 years, will you remember the constraints?) for a little potential (but not even guaranteed) benefit of a few nanoseconds at destruction. If destruction would be so time critical to justify this, you'd better work on specialising allocators than playing with the code safety.

2
  • Did you mean to say "protected and non-virtual" in your bolded statement? Commented Mar 10 at 13:04
  • 1
    @ToddR "non virtual and protected" is to ensure that you stay in control of who can delete if it is non virtual. No I try to answer here OP's question, i.e. there may be situations where you could keep it public and non virtual (although I advise against it in my last paragraph). For example, if you have a class hierarchy mainly meant to reuse code but without a polymorphic design, you could well keep the non virtual public destructor. Same if you use compile-tile polymorphism based on templates. Commented Mar 10 at 23:01
-1

For performance.

Zero-cost abstraction is an important design goal in С++. I.e. if a feature is unused, it should cost nothing. As unconditional use of virtual call would incur additional cost, it conflicts with this design goal and is therefore unacceptable.

So use static dispatch for performance, use dynamic dispatch for flexibility and safety.

On a side note, mandatory virtual destructor may be a requirement for one of many "safe" dialects of C++.

6
  • I'm not sure I understand how cost is a factor here, since the v-table is created either way. Are you saying that simply having a virtual destructor in the base class forces a call to the derived class constructor to be invoked indirectly via the v-table (i.e. the added cost) even when called on an object of the derived class type? Commented Mar 4 at 17:02
  • @ToddR virtual table is not created by default. Also, dynamic dispatch requires additional memory read and is hard to inline. Commented Mar 4 at 17:03
  • @ToddR there is a common fallacy, that an object does not have to have a virtual destructor if it does not have other virtual methods. It still needs it if deletion happens via a pointer to base class. I assume your comment is caused by such invalid assumption. Commented Mar 4 at 17:13
  • 1
    I think that's the opposite of what I'm asking. I know you need a virtual destructor if you want to delete via base class pointer, even if you have no other virtual methods. I'm talking about the case where you have only pure virtual methods, but a protected, non-virtual destructor, thus prohibiting destruction via the base class pointer. When designing the base class, what is the use case where I want to explicitly prevent destruction via base class pointer, intentionally limiting the way someone creating a derived class can delete their object? Commented Mar 4 at 18:33
  • @ToddR Protected destructor does not prohibit destruction via base pointer, as due to chained inheritance, classes have multiple bases. My answer does not suggest to use protected destructors (although they might help in some scenarios, with final hierarchy?). The way to achieve the static dispatch has to be external to the class (unfortunately). Commented Mar 4 at 20:36

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.