Monday, November 18, 2019

C++ virtual destructors, a modern take

Several years ago I created a post where I looked at virtual destructors in C++.  In that post I argued that all C++ classes should have virtual destructors, unless you specifically knew what you were doing and understood the risks.  I originally wrote that before I learned the many benefits of "modem" C++ (aka C++11 and newer).  Modern C++ introduces a few new changes that really improve the language, including changes to virtual methods and class inheritance.  So I would like to revise my suggestions for class designs in C++.

First, and most importantly, it is still a bug to derive from a class that does not have a virtual destructor.  As a developer it is your job to ensure you do not derive from a class with a non-virtual destructor.  At the same time, you should not write a class without a virtual destructor that allows someone else to derive from you.  But the great news is, modern C++ has a solution to both of these problems.


Deriving from a base class without a virtual destructor:
C++ has a new keyword called "override" that tells the compiler to generate an error if the virtual function you have created does not match a virtual function in a higher class.  For example:

class CDerived : public CBase
{
public:
    CDerived();
    virtual ~CDerived() override;
};

In this example "CBase" is the base class.  If CBase has a virtual destructor then this code will compile.  But if CBase does not have a virtual destructor this code will not compile!

The keyword "override" is one of the best new features of modern C++.  Any virtual function should be marked with "override" except the virtual functions in the base class which by definition cannot derive from something else since they are in the base class.


Creating a class 
If you create a class without a virtual destructor, that's fine but someone else might derive from your class without your knowledge or permission.  This would be a bug.  If only there was a way to prevent this from happening.  Good news, in modern C++ there is a way.  Use the keyword "final" to indicate that no class may derive from you.  For example:

class CWidget final
{
public:
    CDerived();
    ~CDerived();
};

In this example I can get away without a virtual destructor because no one could ever derive from my class thanks to the "final" keyword.



Given these new keywords, I would modify my original guidelines as follows.

1.  If you derive from another class, always use "override."
2.  If you create a new class, always use a virtual destructor unless both A) your class does not derive from another class and B) your class is marked with "final."  In this situation, none of your class methods should be marked as virtual.



A few interesting side notes.  First, "override" now supersedes "virtual."  You can use both keywords as I did above, or you can just use "override" which implies "virtual."

A second, more important note, is regarding defaulted destructors.  C++ allows you to omit the destructor, or you can explicitly declare a destructor but use the "default" keyword to omit the destructors definition.  In both cases, the compiler will by default generate a non-virtual destructor.  If you want/need a virtual destructor, write the code yourself (using override) or declare as follows:

virtual ~CWidget() = default;

Related to this last point, the following code is a bug:

class CBase
{
public:
    CBase();
    virtual ~CBase();
};


class CDerived : public CBase

{
public:
    CDerived();
};

Yes the base class has a virtual destructor, and you omitted the destructor so the compiler will generate one for you.  But the generated destructor will not be virtual thus leading to bugs.  To fix this add:

    virtual ~CDerived() override = default;