One common misuse of C++ features is declaring virtual destructors for all classes, including those that do not utilize runtime polymorphism (e.g. data wrapper, container, concurrency control classes, etc.). For a C++ developer working on a common codebase, it is likely a class declaration like the following has been encountered:
Because of the frequency of this misuse, it is even possible to encounter author comments next to non-virtual destructor declarations explaining why these destructors are declared as non-virtual, warning others who may modify the code that this was intentional.
Frequently, the subject of virtual destructors has been discussed online addressing the common questions: “Should every class/Does every class have to have a virtual destructor?” and “When (not) to use virtual destructors?” Depending on whether the question seeks advice or clarification regarding the necessity of virtual destructors, many answers detail when its use is recommended, not recommended, required, or prohibited. The outcomes of declaring destructors virtual, organized into 3 categories and based on their utilization of runtime polymorphism, after closer examination, provides proof that declaring all destructors virtual is an improper practice.
These types of classes exhibit the true use case of virtual destructors. Consider the following pseudo-code:
Declaring the base class destructor as virtual ensures the call of the derived class destructor, when an object of the derived class type is destroyed via a pointer or a reference of the base class type. If
Base1::~Base1 is non-virtual, executing the delete expression would invoke
Base1::~Base1, but not
Derived1::~Derived1, which would result in a leak of resources allocated by
Derived1 other than those inherited from
Well, there is not much more to say... In such a situation, using virtual destructors is a necessity for the utilization of runtime polymorphism and, in fact, this is exactly what they are used for.
What if runtime polymorphism is already in use (i.e. the base class has at least one virtual function that is not its destructor), but polymorphic destruction is not intended in the class design? In the following example, the method of destruction from the previous example has been changed to justify such a scenario:
Because runtime polymorphism is not used in the destruction of derived class instances, allowing the base class destructor to be non-virtual does not prevent the derived class destructor from being called. There is no concern for resource leaks due to missed destructor calls.
Since virtual destructors are not necessary for this case, what are the implications of its use? What is the runtime cost of such virtual function calls?
Runtime cost of a virtual function call: Although the implementation method of virtual functions is not defined by the language specification, compilers generally use a data structure called a virtual function table (vtable), an instance of which is created for each class type that declares or inherits at least one virtual function. For each virtual function of the class, there exists a row in the vtable that contains a pointer to that function. Each instance of a class with a vtable holds a pointer to this vtable as a hidden data member. It is through this pointer to the vtable, that virtual function calls through pointers and references of base class types are directed to the function of the actual object’s type. That is why a call to a virtual function effectively resembles something like the following statement:
Also worth mentioning: it is less likely for virtual functions to be inlined, than non-virtual functions.
It is obvious, that the cost of using a virtual function mechanism is not always negligible. On the other hand, for classes in a hierarchy that already utilize this mechanism (e.g.
Derived2 in the code example), the toll for a class level virtual function table and a pointer to this table in each class instance has already been paid. The extra cost of declaring a base class destructor can be expected as an additional row in the class vtables, as well as access to the appropriate vtable and row during the destructor call.
For most cases, these additional costs are considered relatively low. Therefore, when implementing a design with runtime polymorphism, but without polymorphic destruction of derived class objects, declaring destructors virtual is a good practice as a safety mechanism for unintended polymorphic destruction through client mistake. (Please note, destructors of base classes can be declared in the protected class scope in order to prevent compilation of such client code, if public scope is not required by design.)
Still remaining, are all the other classes that do not use runtime polymorphism. In other words, those which do not need any virtual functions. Some classes are not designed to be base classes at all, as exhibited in
SomeDataWrapper from the first code snippet. Furthermore, a base class may not use runtime polymorphism, as shown in the following example:
Declaring virtual destructors for these classes can incur costs for the virtual function mechanism, although they will not be utilized. The runtime costs of virtual destructors have already been discussed for Type 2 classes. However, these costs are not the same for classes without any other virtual functions.
Below is a list of the potential disadvantages of declaring the destructor virtual in a class not utilizing runtime polymorphism, the importance of each depending on the particular use case:
std::memcmpwith an object of this class type. Additionally, because memory layout of such objects is no longer simple, extra measures are needed when passing the class type to another language for developing cross-language software
virtualkeyword next to the name of a destructor function in a non-runtime polymorphic class declaration implies runtime polymorphism, as this is the sole purpose of virtual functions. (When encountering a virtual destructor, a reader may look for other virtual function declarations as the next thing to do, because the use of runtime polymorphism is a fundamental class trait.) This is always a valid concern, even if all other drawbacks are negligible for a given class.
Contrarily, the only advantage of declaring destructors of such classes as virtual is to prevent against possible client misuse by destructing derived class objects through pointers or references of base class types. As aforementioned, the toll can be too costly for this type of advantage, especially to make it a habit.
In fact, virtual destructors are still reasonable for non-runtime polymorphic base classes for which performance and size are not important concerns and all costs are acceptable. This is true particularly when the destructor must be public, keeping in mind that protected base class destructors prevent this misuse as well. It is also worth reminding that base classes that do not have any virtual functions are not very common in object-oriented design.
However, virtual destructors do not make much sense for classes that are not designed to be base classes (a trait that can be enforced with the final identifier since C++11). In general, blindly declaring every destructor as virtual is a bad practice and can potentially lead to a significant waste of resources.
Bad practices such as this, are typically a result of misguidance for novice C++ programmers. However, in the case of experienced programmers, this can better be explained with a tendency of overlooking the rationale and costs of using specific language features, which leads to an incorrect level of abstraction in the coding process, resulting in bad code. On the other hand, doing this deliberately may well point to a zealous belief in defensive programming to the point of significant performance loss, as well as making the code less coherent.
Upon examining the advantages and disadvantages of using virtual destructors, the best practice can be summarized by Scott Meyers, from his book Effective C++:
“Polymorphic base classes should declare virtual destructors. If a class has any virtual functions, it should have a virtual destructor. Classes not designed to be base classes or not designed to be used polymorphically should not declare virtual destructors.” (p. 58)