c++constructorvirtual-inheritancecompiler-buggcc5

Invoking constructors during virtual inheritance with C++


This is a question I encountered while reading this section on learncpp.com. I used the code listed here, then made slight alterations for testing.

Background

Virtual inheritance creates a common reference to the base class, which has two effects.

First, it removes ambiguity because only once copy of the base's members are created (e.g. adding a print() function to PoweredDevice and calling it in main() would otherwise cause a compiler error).

Second, the most derived class becomes responsible for invoking the base constructor. If one of the intermediate classes attempts to invoke the base constructor in an initialization list, the call should be ignored.

The Problem

When I compile and run the code, it returns:

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

It should return:

PoweredDevice: 3
Scanner: 1
Printer: 2

When I follow the execution using GDB (7.11.1), it shows that the intermediate functions are also calling PoweredDevice through the initialization list--but these should be ignored. This multiple initialization of PoweredDevice does not lead to ambiguity of any members, but does trouble me because code is executing multiple times when it should only happen once. For a more complicated problem I would not be comfortable using virtual inheritance.

Why are these intermediate classes still initializing the base? Is it a quirk with my compiler (gcc 5.4.0) or am I misunderstanding how virtual inheritance works?

Edit: Code

#include <iostream>
using namespace std;

class PoweredDevice
{
public:
    int m_nPower;
public:
    PoweredDevice(int nPower)
        :m_nPower {nPower}
    {
        cout << "PoweredDevice: "<<nPower<<endl;
    }
    void print() { cout<<"Print m_nPower: "<<m_nPower<<endl; }
};

class Scanner : public virtual PoweredDevice
{
public:
    Scanner(int nScanner, int nPower)
        : PoweredDevice(nPower)
    {
        cout<<"Scanner: "<<nScanner<<endl;
    }
};

class Printer : public virtual PoweredDevice
{
public:
    Printer(int nPrinter, int nPower)
        : PoweredDevice(nPower)
    {
        cout<<"Printer: "<<nPrinter<<endl;
    }
};

class Copier : public Scanner, public Printer
{
public:
    Copier(int nScanner, int nPrinter, int nPower)
        :Scanner {nScanner, nPower}, Printer {nPrinter, nPower}, PoweredDevice {nPower}
    { }
};

int main()
{
    Copier cCopier {1,2,3};
    cCopier.print();
    cout<<cCopier.m_nPower<<'\n';
    return 0;
}

Solution

  • This appears to be a GCC bug, triggered when uniform initialisation is used with virtual inheritance.


    If we change:

    Copier(int nScanner, int nPrinter, int nPower)
        :Scanner {nScanner, nPower}, Printer {nPrinter, nPower}, PoweredDevice {nPower}
    { }
    

    to:

    Copier(int nScanner, int nPrinter, int nPower)
        :Scanner (nScanner, nPower), Printer (nPrinter, nPower), PoweredDevice (nPower)
    { }
    

    The error disappears, and it behaves as expected:

    PoweredDevice: 3
    Scanner: 1
    Printer: 2
    Print m_nPower: 3
    3
    

    Both Clang and Visual Studio are able to compile the original code properly, and give the expected output.