4
votes

C++ FAQs item 20.05:

"Virtual base classes are special, their destructors are called at the end of the most derived class' destructor (only)."

I dont really understand how this fits in with the typical:

"data member destructors first, then base class destructors" rule

How are virtual base classes special? I cannot tell what the above means :s

2
Virtual base class destructors are called after a derived class's destructor because they provide the cleanup functionality for an abstract type that is expected to be implemented in a sub class.Ryan J
This answer might be useful but is not a duplicate.merlin2011
Virtual base constructors are special - they are always called first, before the constructors of any non-virtual base classes. Destruction is always performed in reverse order of construction. Thus, virtual base destructors are special in that they are always called last.Igor Tandetnik
A virtual base (emphasis added on the virtual) fulfills the diamond pattern in that a single "instance" of the base is shared by multiple intermediate derivations before arriving at the main event (the most-derived). Just as the most-derived constructors must directly invoke the constructs of the virtual base, so goes destruction at the end of the most derived destructor. This is different than the traditional regular (non-virtual) base. It sounds like you're confused between a virtual base, and a virtual destructor.WhozCraig
That's what I'm saying: the "specialness" is really in the order of construction. The order of destruction just follows from that. Why the FAQ chose to emphasize destructors, I'm not sure. Perhaps the phrase makes more sense in context.Igor Tandetnik

2 Answers

3
votes

The entire paragraph of the book you are quoting was describing the order of destructors. Normally, in the class declaration, the order of the classes listed for inheritance determines the order of their construction, and then they are destructed in reverse order.

A virtual base class means virtual inheritance was used on it:

struct Base {};

struct D : virtual Base {};
struct D1 : D, virtual Base {};
struct D2 : virtual Base, D {};

ASCII art alert:

        Base            Base
         | \             |  \
        /_\ \            |  /_\
         |   \           |    \
         D  /_\          |     D
         |   /           |    /
        /_\ /           /_\ /_\
         | /             |  /
         D1              D2

The multiple inheritance collapses the diamond into a single line, in this case. But, the point is still illustrated. The order of inheritance for D1 and D2 doesn't matter. The order in which the destructors for D and Base are called will be the same for both of them.

3
votes

The key property of virtual base classes is that they always produce a single unique base subobject in any object of derived class. That's exactly what's special about virtual base classes and that makes them different from regular base classes, which can produce multiple subobjects.

For example, in this hierarchy

struct B {};
struct M1 : B {};
struct M2 : B {};
struct D : M1, M2 {}

there's no virtual inheritance. All bases are inherited using regular inheritance. In this case class D will contain two independent subobjects of type B: one brought in by M1, another - by M2.

+-> D <-+
|       |
M1      M2
^       ^
|       |
B       B    <- there are two different `B`s in `D`

The task of properly destructing all subobjects when destructing D is trivial: each class in the hierarchy is responsible of destructing its direct bases and only its direct bases. That simply means that destructor of M1 calls the destructor of its own B subobject, destructor of M2 calls the destructor of its own B subobject, while destructor of D calls the destructors of its M1 and M2 subobjects.

Everything works out nicely in the above destruction schedule. All subobjects get destructed, including both subobjects of type B.

However, once we switch to virtual inheritance things become more complicated

struct B {};
struct M1 : virtual B {};
struct M2 : virtual B {};
struct D : M1, M2 {}

Now there only one subobject of type B in D. Both M1 and M2 see and share the same subobject of type B as their base.

+-> D <-+
|       |
M1      M2
^       ^
|       |
+-- B --+    <- there is only one `B` in `D`

If we make a naive attempt to apply the previous destruction schedule to this hierarchy, we'll end up with the B subobject getting destructed twice: M1 calls the destructor of B subobject, and M2 calls the destructor of the very same B subobject.

That is, of course, completely unacceptable. Each subobject has to be destructed once and only once.

In order to solve this problem, when M1 and M2 are used as base subobjects of D, these are explicitly prohibited to call the destructor of their B subobject. The responsibility of calling the destructor of B is assigned to D's destructor. Class D, when used as a complete independent object (i.e. serves as a most derived class), knows that there's only one B in it and knows that the destructor of B has to be called only once. So, the destructor of class D will call the destructor of B for that unique base subobject of type B. Meanwhile, destructors of M1 and M2 will not even attempt to call the destructor of B.

That's how it works with virtual inheritance. And that what the rule you quoted says basically. The parts that says that virtual bases' destructors are called last simply means that each class'e destructor calls destructors for its direct regular base classes, and only after that, if necessary, it calls destructors of its virtual base classes (possibly indirect). In the above example, the destructor of D calls destructors of M1 and M2 and only after that it calls the destructor of B.