Search

12.7 — Virtual base classes

Last chapter, in lesson 11.7 -- Multiple inheritance, we left off talking about the “diamond problem”. In this section, we will resume this discussion.

Note: This section is an advanced topic and can be skipped or skimmed if desired.

The diamond problem

Here is our example from the previous lesson (with some constructors) illustrating the diamond problem:

Although you might expect to get an inheritance diagram that looks like this:

If you were to create a Copier class object, by default you would end up with two copies of the PoweredDevice class -- one from Printer, and one from Scanner. This has the following structure:

We can create a short example that will show this in action:

This produces the result:

PoweredDevice: 3
Scanner: 1
PoweredDevice: 3
Printer: 2

As you can see, PoweredDevice got constructed twice.

While this is often desired, other times you may want only one copy of PoweredDevice to be shared by both Scanner and Printer.

Virtual base classes

To share a base class, simply insert the “virtual” keyword in the inheritance list of the derived class. This creates what is called a virtual base class, which means there is only one base object that is shared. Here is an example (without constructors for simplicity) showing how to use the virtual keyword to create a shared base class:

Now, when you create a Copier class, you will get only one copy of PoweredDevice that will be shared by both Scanner and Printer.

However, this leads to one more problem: if Scanner and Printer share a PoweredDevice base class, who is responsible for creating it? The answer, as it turns out, is Copier. The Copier constructor is responsible for creating PoweredDevice. Consequently, this is one time when Copier is allowed to call a non-immediate-parent constructor directly:

This time, our previous example:

produces the result:

PoweredDevice: 3
Scanner: 1
Printer: 2

As you can see, PoweredDevice only gets constructed once.

There are a few details that we would be remiss if we did not mention.

First, virtual base classes are always created before non-virtual base classes, which ensures all bases get created before their derived classes.

Second, note that the Scanner and Printer constructors still have calls to the PoweredDevice constructor. When creating an instance of Copier, these constructor calls are simply ignored because Copier is responsible for creating the PoweredDevice, not Scanner or Printer. However, if we were to create an instance of Scanner or Printer, those constructor calls would be used, and normal inheritance rules apply.

Third, if a class inherits one or more classes that have virtual parents, the most derived class is responsible for constructing the virtual base class. In this case, Copier inherits Printer and Scanner, both of which have a PoweredDevice virtual base class. Copier, the most derived class, is responsible for creation of PoweredDevice. Note that this is true even in a single inheritance case: if Copier was singly inherited from Printer, and Printer was virtually inherited from PoweredDevice, Copier is still responsible for creating PoweredDevice.

Fourth, a virtual base class is always considered a direct base of its most derived class (which is why the most derived class is responsible for its construction). But classes inheriting the virtual base still need access to it. So in order to facilitate this, the compiler creates a virtual table for each class directly inheriting the virtual class (Printer and Scanner). These virtual tables point to the functions in the most derived class. Because the derived classes have a virtual table, that also means they are now larger by a pointer (to the virtual table).

12.8 -- Object slicing
Index
12.6 -- Pure virtual functions, abstract base classes, and interface classes

59 comments to 12.7 — Virtual base classes

  • Hi Alex!

    The call to the @PoweredDevice constructor should be moved before the ones to @Scanner and @Printer.

    Your order produces warnings
    warning: base class 'Printer' will be initialized after base 'PoweredDevice' [-Wreorder]
    warning: base class 'Scanner' will be initialized after base 'PoweredDevice' [-Wreorder]

  • Viktar

    Hi Alex, there's one thing I don't understand.

    In constructors in derived class where we need to instantiate Base class there is some difference in initialization.

    For example, if you try to initialize Scanner, Printer and PoweredDevice in Copier in uniform manner (Like Scanner{scanner, power}, Printer{printer, scanner}, etc.) the behaviour is not the same as Scanner(scanner, power), Printer(printer, power), etc.

    I checked chapter "2.1 — Fundamental variable definition, initialization, and assignment" where it says Rule: If you’re using a C++11 compatible compiler, favor uniform initialization

    And additional I checked 8.5 "Constructors" and where it says Rule: Use direct or uniform initialization with your classes.
    + "8.5a — Constructor member initializer lists" the same Rule: Favor uniform initialization over direct initialization if your compiler is C++11 compatible. Actually I didn't find any strings where you're using uniform initialization for to instantiate base class.

    + 11.4 — Constructors and initialization of derived classes - as I see you haven't mentioned uniform initialization at all.

    Could you describe this difference a little bit clearly. Thank you.

    P.S. I use CodeBlocks 17.2 on Debian 9.

    • Hi Viktar!

      I'm unable to reproduce what you described. Both

      and

      (should) produce the same results. Did you do anything other than that? If so, what? If no, which compiler are you using? Make sure you're running it's newest version.

      > Actually I didn't find any strings where you're using uniform initialization
      Alex doesn't use uniform initialization himself, because he's old :). You should do so.

      • Viktar

        If I use this code

        then result is expected.

        In the same time when I use this code

        I've got next result :

        PoweredDevice: 3
        PoweredDevice: 3
        Scanner: 1
        PoweredDevice: 3
        Printer: 2

        The compiler version is: gcc (Debian 6.3.0-18+deb9u1) 6.3.0 20170516

        Looks like gcc is quite old

        • It looks like @PoweredDevice is getting called from all three @Printer, @Scanner and @Copier, which should not be happening, because only the call to @PoweredDevice from @Copier has an effect on the resulting object. It could be that the standard allows the behavior you're experiencing, nonetheless I suggest you to update g++ to get consistent results. g++ 8.2.1 produces the same output no matter which initialization is used.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]