59
votes

Everyone knows that the desructor of base class usually has to be virtual. But what is about the destructor of derived class? In C++11 we have keyword "override" and ability to use the default destructor explicitly.

struct Parent
{
  std::string a;
  virtual ~Parent()
  {
  }

};

struct Child: public Parent
{
  std::string b;
  ~Child() override = default;
};

Is it correct to use both keywords "override" and "=default" in the destructor of Child class? Will compiler generate correct virtual destructor in this case?

If yes, then can we think that it is good coding style, and we should always declare destructors of derived classes this way to ensure that base class destructors are virtual?

7
Might as well do static_assert(std::has_virtual_destructor<Parent>::value, "contract violated");milleniumbug
Note that it isn't always a requirement that the base class destructor be virtual. So this is only (possibly) a good idea if that is a requirement.juanchopanza
If it works, I like it, but milleniumbug's is better (much clearer intent). On the other hand, Stroustrup hates "coding standard" constructs that guard against common errors, and insists the compiler should generate suitable warnings, instead.Kenny Ostrom
I think @milleniumbug's approach expresses the intent clearly. If I came across ~Child() override = default; in a code base I might just remove the line.juanchopanza
"it may be worthwhile to take some time out to study some C++" -- please see "blaming the programmer" at the end of this post. Also, note that I didn't actually say that I don't understand the static_assert, just that it's more confusing than the override version. Which is true, because it's longer, more verbose, and uses a comparatively obscure feature of the standard library.Kyle Strand

7 Answers

29
votes

Is it correct to use both keywords "override" and "=default" in the destructor of Child class? Will compiler generate correct virtual destructor in this case?

Yes, it is correct. On any sane compiler, if the code compiles without error, this destructor definition will be a no-op: its absence must not change the behavior of the code.

can we think that it is good coding style

It's a matter of preference. To me, it only makes sense if the base class type is templated: it will enforce a requirement on the base class to have a virtual destructor, then. Otherwise, when the base type is fixed, I'd consider such code to be noise. It's not as if the base class will magically change. But if you have deadheaded teammates that like to change things without checking the code that depends on what they may be possibly breaking, it's best to leave the destructor definition in - as an extra layer of protection.

19
votes

override is nothing more than a safety net. Destructor of the child class will always be virtual if base class destructor is virtual, no matter how it is declared - or not declared at all (i.e. using implicitly declared one).

9
votes

According to the CppCoreGuidelines C.128 the destructor of the derived class should not be declared virtual or override.

If a base class destructor is declared virtual, one should avoid declaring derived class destructors virtual or override. Some code base and tools might insist on override for destructors, but that is not the recommendation of these guidelines.

UPDATE: To answer the question why we have a special case for destructors.

Method overriding is a language feature that allows a subclass or child class to provide a specific implementation of a method that is already provided by one of its superclasses or parent classes. The implementation in the subclass overrides (replaces) the implementation in the superclass by providing a method that has same name, same parameters or signature, and same return type as the method in the parent class.

In other words, when you call an overridden method only the last implementation of that method (in the class hierarchy) is actually executed while all the destructors (from the last child to the root parent) must be called to properly release all the resources owned by the object.

Thus we don't really replace (override) the destructor, we add additional one into the chain of object destructors.

UPDATE: This CppCoreGuidelines C.128 rule was changed (by 1448, 1446 issues) in an effort to simplify already exhaustive list of exceptions. So the general rule can be summarized as:

For class users, all virtual functions including destructors are equally polymorphic.

Marking destructors override on state-owning subclasses is textbook hygiene that you should all be doing by routine (ref.).

8
votes

There is (at least) one reason for using override here -- you ensure that the base class's destructor is always virtual. It will be a compilation error if the derived class's destructor believes it is overriding something, but there is nothing to override. It also gives you a convenient place to leave generated documentation, if you're doing that.

On the other hand, I can think of two reasons not to do this:

  • It's a little weird and backwards for the derived class to enforce behavior from the base class.
  • If you define a destuctor in the header (or if you make it inline), you do introduce the possibility for odd compilation errors. Let's say your class looks like this:

    struct derived {
        struct impl;
        std::unique_ptr<derived::impl> m_impl;
        ~derived() override = default;
    };
    

    You will likely get a compiler error because the destructor (which is inline with the class here) will be looking for the destructor for the incomplete class, derived::impl.

    This is my round-about way of saying that every line of code can become a liability, and perhaps it's best to just skip something if it functionally does nothing. If you really really need to enforce a virtual destructor in the base class from the parent class, someone suggested using static_assert in concert with std::has_virtual_destructor, which will produce more consistent results, IMHO.

4
votes

I think "override" is kind of misleading on destructor. When you override virtual function, you replace it. The destructors are chained, so you can't override destructor literally

3
votes

The CPP Reference says that override makes sure that the function is virtual and that it indeed overrides a virtual function. So the override keyword would make sure that the destructor is virtual.

If you specify override but not = default, then you will get a linker error.

You do not need to do anything. Leaving the Child dtor undefined works just fine:

#include <iostream>

struct Notify {
    ~Notify() { std::cout << "dtor" << std::endl; }
};

struct Parent {
    std::string a;
    virtual ~Parent() {}
};

struct Child : public Parent {
    std::string b;
    Notify n;
};

int main(int argc, char **argv) {
    Parent *p = new Child();
    delete p;
}

That will output dtor. If you remove the virtual at Parent::~Parent, though, it will not output anything because that is undefined behavior, as pointed out in the comments.

Good style would be to not mention Child::~Child at all. If you cannot trust that the base class declared it virtual, then your suggestion with override and = default will work; I would hope that there are better ways to ensure that instead of littering your code with those destructor declarations.

0
votes

Though destructors are not inherited there is clear written in the Standard that virtual destructors of derived classes override destructors of base classes.

From the C++ Standard (10.3 Virtual functions)

6 Even though destructors are not inherited, a destructor in a derived class overrides a base class destructor declared virtual; see 12.4 and 12.5.

On the other hand there is also written (9.2 Class member)

8 A virt-specifier-seq shall contain at most one of each virt-specifier. A virt-specifier-seq shall appear only in the declaration of a virtual member function (10.3).

Though destructors are called like special member functions nevertheless they are also member functions.

I am sure the C++ Standard should be edited such a way that it was unambiguous whether a destructor may have virt-specifier override. At present it is not clear.