Dear Readers!

Today I’ll be discussing destructors. So why do they warrant a post, when people write them only in special cases and the compiler always provides one anyway? It is exactly because of that. Since the compiler generates them all the time, we keep desturctors in the back of our heads and only think about them when some special memory or state management is required, like delete_ing objects created with _new in a constructor (e.g. RAII), or resetting some hardware to a safe state. Unfortunately there are some situations though, when we only think that it is safe to rely on the compiler to generate the proper destructor for us, but reality can tell us otherwise. Let’s examine such a case:

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Furniture {
public:
    // pure virtual methods
    // ...

private:
    // some data members
};

class Table : public Furniture {
public:
    // constructors, operators
    // overridden methods
    
    ~Table();   // special memory management is needed
    
private:
    // more data members
};

// other classes derived from Furniture 


// ...


Table* coffeeTable = new Table();
// do something with coffeeTable
delete coffeeTable;

Furniture* someFurniture = new Table();
// use someFurniture
delete someFurniture;

The code above looks perfectly valid from first glance, but as soon as we arrive at delete someFurniture we will get undefined behavior. But why? The problem with the delete call on someFurniture in this case is, that it will trigger Furniture’s automatically generated destructor instead of Table’s, which is not what we originally intended. Obviously, since we are calling the wrong destructor (Table is not exactly a Furniture, but only a kind of a Furniture), there will be a memory management problem. Just try to imagine how Funrniture’s destructor is supposed to delete the “more data members” found in Table, when it doesn’t even know they exist. This undefined behavior can be dismissed by augmenting the Furniture base class in the following manner:

C++

1
2
3
4
5
6
7
8
9
10
class Furniture {
public:
    // pure virtual methods
    
    virtual ~Furniture() {}
    // ...

private:
    // some data members
};

We defined an empty destructor and we made it virtual. This way whenever we arrive at delete someFurniture, the name of Furniture’s destructor will be converted by the compiler into an index into vtbl instead of having a normal function call, and the net result is the invocation Table’s destructor. This is the behavior we need in most cases, and as such we can think of the virtual keyword in a way, that it tells the compiler to replace a normal function call with an index into the appropriate vtbl, where a pointer to the needed function can be found. It must be noted though, that nothing prevents a pointer in vtbl to point back to a base class function if it isn’t pure virtual and there is no function to override it in a derived class (e.g. resizeHeight(double) function, see below). This means that resizeHeight(double) is still an index into vtbl, despite the fact that only Furniture has such a function.

I should point out here, that I’m not entirely certain that the compiler does work as described above, but thinking about it in such a fashion makes it easier to remember the proper usage of the virtual keyword.

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Furniture {
public:
    // pure virtual methods
    virtual void resizeHeight(double d);

    virtual ~Furniture() {}

private:
    // some data members
};

class Table : public Furniture {
public:
    // constructors, operators
    // overridden methods
    
    ~Table();
    
private:
    // more data members
};

// ...

Furniture* coffeeTable = new Table();
coffeeTable->resizeHeight(3.5); // will call resizeHeight inside Furniture;

Anyway, since the discussion is now starting to drift towards class hierarchy navigation, which is certainly worth a post in itself, I’ll stop here with today’s lesson:

Don’t forget to define a virtual destructor in your base classes, even if no memory or state management is required.

As always, thanks for reading.