Understanding Virtual Destructors in C++

Introduction

C++ is a powerful language, but with great power comes great responsibility — especially regarding memory management. One crucial aspect of writing robust C++ code is understanding and correctly using virtual destructors. In this post, we’ll dive deep into virtual destructors: what they are, why they’re necessary, and how to use them effectively.

C++ Destructors: Properties and Invocation Order

Destructors are special member functions in C++ responsible for cleaning up resources when an object is destroyed. They have specific properties and a defined execution order, particularly within inheritance hierarchies.

Invocation Order (in Inheritance)

  • Constructors: Run from the base class to the most derived class.
  • Destructors: Run in reverse — from the most derived class back to the base class.
  • Rationale: Destroying derived class resources first ensures that all resources are properly released before the base class cleans up.

Key Properties of Destructors

  • Automatic Invocation: Called automatically when an object goes out of scope (stack allocation) or when delete is called (heap allocation).
  • No Parameters: Destructors cannot accept arguments.
  • One Per Class: A class can have only one destructor.
  • Non-Inheritable: Destructors are not inherited, although a derived class’s destructor implicitly calls the base class’s destructor.
  • Can Be Virtual: Declaring a destructor virtual in a base class is crucial for correct behavior during polymorphic deletion.

What Is a Virtual Destructor?

In C++, a virtual destructor is a destructor declared with the virtual keyword in the base class. Its main purpose is to ensure that when you delete a derived class object through a pointer to its base class, the derived class’s destructor is called before the base class’s destructor. “In a polymorphic base class, the destructor should almost always be virtual” (Stroustrup, 2013).

Why Are Virtual Destructors Important?

Consider a common situation:

  • You have a base class Animal and a derived class Dog.
  • Dog allocates memory dynamically (e.g., for the dog’s breed name).
  • You upcast the Dog object to an Animal* pointer.
  • You delete the object through the Animal* pointer.

If Animal‘s destructor is not virtual, only the Animal destructor is called, skipping Dog’s destructor—and leaking memory.

Danger of Missing Virtual Destructors

The danger of missing virtual destructors is illustrated in Listing 1, Listing 2 and Listing 3. The output of Listing 3 is shown in Figure 1.

Listing 1: Base Class with Non-Virtual Destructor.

Listing 1 declares a C++ class named AnimalNoVirtual:

  • When an AnimalNoVirtualobject is created, its constructor prints “Animal Constructor” to the console.
  • When an AnimalNoVirtualobject is destroyed, its destructor prints “Animal Destructor” to the console.
  • The destructor ~AnimalNoVirtual()is not declared virtual. This leads to resource leaks during polymorphic deletion.
Listing 2: DogNoVirtual Derived Class.

Listing 2 defines a class DogNoVirtual that publicly inherits from the AnimalNoVirtual base class:

  • Constructor takes a breed name, allocates memory to store it in the breed pointer member, and prints “Dog Constructor”.
  • Destructor prints “Dog Destructor” and frees the memory allocated for the breed string to prevent memory leaks when this specific destructor is called.
Listing 3: Demonstrating Non-Virtual Destructor Problem.
Figure 1: Output: Non-Virtual Destructor Problem.

Listing 3 and Figure 1 demonstrate the non-virtual destructor problem:

  • Creates aDogNoVirtualobject dynamically but stores the pointer in a base class pointer (AnimalNoVirtual*).
  • Attempts to clean up the allocated memory using delete on the base class pointer (animalPtr).
  • DogNoVirtual destructor skipped during deletion.

Virtual Destructors to Rescue

The solution to the virtual destructor problem (destructor is virtual) is shown in Listing 4. Figure 2 shows this will correctly call the derived class’s destructor first, followed by the base class destructor.

Listing 4: Polymorphic Base Class with Virtual Destructor.
Figure 2: Output of Polymorphic Base Class with Virtual Destructor.

When to Use Virtual Destructors

  • If a class is designed to be a base class, you should almost always declare its destructor as virtual.
  • The small overhead of virtual dispatch is well worth the memory safety benefits (Meyers, 2005).
  • Alternative: If you do not want objects to be deleted through a base pointer, make the destructor protected and non-virtual. This way, deletion through a base pointer becomes a compile-time error. This is illustrated in Listing 5 and Listing 6.
Listing 5: Base Class with Protected Destructor Pattern.
Listing 6: Demonstrating Protected Destructor Usage.

But why does the protected destructor pattern behave this way? The reason is that with a protected destructor, the destructor can only be called by derived classes or member functions, not directly from outside code , like main (Listing 6).

Conclusion

Virtual destructors are a fundamental concept in C++ that help you write safe, reliable, and leak-free code. If your class is intended for inheritance, make its destructor virtual. Doing so ensures that destruction happens properly across inheritance hierarchies, avoiding subtle bugs and resource leaks.

References

  • Stroustrup, B. (2013). The C++ Programming Language (4th ed.). Addison-Wesley.
  • Meyers, S. (2005). Effective C++: 55 Specific Ways to Improve Your Programs and Designs (3rd ed.). Addison-Wesley.
  • Sutter, H. (2000). Virtuality. Retrieved from GotW
  • ISO C++ Foundation. (2023). C++ Core Guidelines: C.35: A base class destructor should be either public and virtual, or protected and nonvirtual. Retrieved from C++ Core Guidelines

AI Assistance Disclosure

This article was drafted with editorial assistance from AI and all code was reviewed and tested for accuracy.

Appendix

Code listing: Link to code examples

Leave a Reply

Your email address will not be published. Required fields are marked *