Destructors

Destructors are one of those inescapable concepts in C++. We’ve all used them, many times without even really thinking about it. But how do destructors work? What can and can’t you do with destructors? There’s a lot more complexity to this seemingly simple concept than meets the eye.
In the “usual” case, a destructor is the special member function of a class that is called when an object is being torn down. This is likely the case that immediately comes to mind when you think about destructors.

class F {
public:
~F() {}
};
F *f = new F;
delete f; // calls F::~F

The first thing I want to talk about is how the above code actually “works.” In general, the compiler automatically inserts a hidden member function into class F. If you’ve ever looked at disassembly in MSVC, you may have seen this referred to as the “scalar deleting destructor.” This hidden class method performs two main operations — first it calls the destructor on F, and then it calls the operator delete method. Its implementation looks something like this:

You may not have been aware of it, but destructors can be called like normal instance methods, and operator delete is a regular method call as well. In fact, explicitly calling the destructor as a method crops up in several places. For instance, with discrimated unions or to destroy objects created with the placement new operator. This is the magic that makes the delete operator work — the compiler inserts a function into the class for you, and calls it automatically. This function is responsible for starting the destructor chain firing, and once the chain has completed, it will call operator delete to free the memory. One interesting thing to note about this is that throwing an exception from a destructor results in a memory leak!

So what happens when the destructor is called? Obviously, your class destructor method is executed if you have defined one. But more than that happens too — if your class has data members, those will get their destructors called as well. If your class inherits from another class, the base class will have its destructor called too. Let’s look at another example:

The first thing to remember is that this isn’t really polymorphism at all — there are no virtual methods on any of the classes in the hierarchy! So this does not behave how you would expect, since the static type of the variable will be used to determine what gets called. In this case, E::~E will be called, then D::~D, then C::~C. F and F::b’s destructors will never be called! In order for this to work properly, we need to add a virtual destructor at the base class level. If you were to declare C’s destructor as being virtual, then you would get the same destruction behavior for E *f as you do for F *f.

The way virtual destructors are initially dispatched is the same as any virtual method, which you can read more about here. But once the initial dispatch has happened, explicitly qualified calls are required to traverse up the destructor chain. Without the qualified calls, you would end up simply recursing into the most-derived destructor! But what happens if you call a destructor explicitly — will it too use virtual dispatch? The answer is: yes. It’s just a virtual method like any other.

E *f = new F;
f->~E();

This will call (in order) F::~F, B::~B, E::~E, D::~D, C::~C.

However, just because destructors seem to look like normal function calls doesn’t mean they actually are. There are some properties specific to destructors that make them distinct. For instance, it is illegal to take the address of a destructor (your compiler should give you an error if you attempt it). You cannot declare a const, volatile or static destructor. You can call the destructor on a const object with impunity.

const E *f = new F;
f->~E(); // Not an error

Also, the methods you can call from your destructor is restricted in a similar fashion to what you can call from your constructor. Once the destructor has been invoked for an object, that object no longer exists. Calling virtual functions from within your destructor behaves the same as calling virtual functions from within your constructor — it does not call the most-derived function as you might expect. Instead, it calls the most-derived function on the object which still exists (so it can only call functions at the destructed-class level or higher in the class hierarchy). So as you can see, destructors are a bit special in terms of their semantics when compared to other class member functions.

I’d like to switch gears and talk about when you need to define your own destructor, and when you don’t. The compiler will automatically generate a destructor for you in the event you do not define one yourself. But the generated destructor does not always do the “right” thing like you might expect. For instance, it will not free data members that are pointers; just value types. So if your class contains pointer member variables, you need to define your own destructor to explicitly free your resources. But the single thing which likely confuses most programmers at some point in their career is that the compiler will not implicitly define a virtual destructor. Instead, it will define a non-virtual destructor which can lead to memory leaks if the class is used polymorphically. Consider:

C will have a destructor implicitly defined for it by the compiler, and that implicit destructor will destroy the B object. However, if you run the code, you will only see “B” printed and not “D”, even though the dynamic type of c is D*. This is because the implicit destructor is non-virtual. So the rule of thumb is: if your class declares any virtual functions, then it should explicitly define a virtual destructor as well, even if it is empty. Some people will suggest you always define a virtual destructor in every class, but keep in mind that since a destructor behaves like any other method to a certain extent, adding a virtual destructor to a class which is otherwise non-virtual will still add a vtable and the associated overhead. So if your class does not define any virtual functions, there is no need to define a virtual destructor. Furthermore, if your class is inheriting virtual methods including a virtual destructor, there is no need to explicitly define an empty, virtual destructor in the derived class. The implicit destructor will also be virtual (as is the case with all virtual methods). By the way, this is one place where the new C++11 feature of defaulted functions comes in handy. I strongly prefer to see virtual ~C() = default; instead of virtual ~C() {}, because the former is explicit in that it’s using the default behavior, and the latter looks like someone forgot to implement something.

Destructors are an integral part of C++ object management, and having a firm understanding of what they do and how they do it is important to writing correct code. The key take-home points from this are:

Destructors are called in reverse order from their declarations. This includes class member variables

Destructors operate with normal method dispatch. So if your class is polymorphic, your destructor should be virtual as well or else you will have memory leaks

A lot of destructor code is written for you. There’s rarely a need to have an empty destructor since the compiler will implicitly define one for you. But it won’t implicitly define a virtual destructor

True, I was talking in terms of the base class instead of the derived class. A few paragraphs further up, I pointed out:

Furthermore, if your class is inheriting virtual methods including a virtual destructor, there is no need to explicitly define an empty, virtual destructor in the derived class. The implicit destructor will also be virtual (as is the case with all virtual methods).

So once you’ve defined a virtual destructor, all derived classes will have an implicit virtual destructor instead of a non-virtual one.

Technically, you don’t *need* to create your own destructor. The compiler will provide a default one for you; but it doesn’t actually do much for you (such as freeing resources). And it always defines the defaulted destructor as being non-virtual, which leads to destructors not being called as you might expect.

@PAi: The call to delete c; works because the static type of c is B, and B::~B is a virtual method, so the behavior of the delete expression is well-defined ([expr.delete]p3). Since B::~B is virtual, C::~C is virtual as well ([class.dtor]p9), so C::~C is called, and then the usual destructor ordering functionality is handled ([class.dtor]p8) which doesn’t require virtual method dispatch.

The call to delete a is undefined behavior because the static type of a is A, but the dynamic type is C. Since A::~A is not declared as virtual, the behavior is undefined per [expr.delete]p3. Casting the static type to be C causes the undefined behavior to go away because C::~C is implicitly virtual thanks to B::~B being explicitly virtual.

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

Comment

Name *

Email *

Website

Who

Aaron Ballman is a software engineer for GrammaTech. He has almost two decades of experience writing cross-platform frameworks in C/C++, compiler & language design, and software engineering best practices and is currently a voting member of the C (WG14) and C++ (WG21) standards committees.

In case you can't figure it out easily enough, the views expressed here are my personal views and not the views of my employer, my past employers, my future employers, or some random person on the street. Please yell only at me if you disagree with what you read.