Это вопрос, с которым я столкнулся при чтении этот раздел на learncpp.com. Я использовал приведенный здесь код, затем сделал небольшие изменения для тестирования.
Фон
Виртуальное наследование создает общую ссылку на базовый класс, что имеет два эффекта.
Во-первых, он устраняет неоднозначность, потому что только после создания копии членов базы (например, добавление функции print () в PoweredDevice и вызов ее в main () в противном случае может вызвать ошибку компилятора).
Во-вторых, самый производный класс становится ответственным за вызов базового конструктора. Если один из промежуточных классов пытается вызвать базовый конструктор в списке инициализации, вызов должен быть игнорируются.
Эта проблема
Когда я компилирую и запускаю код, он возвращает:
PoweredDevice: 3
PoweredDevice: 3
Scanner: 1
PoweredDevice: 3
Printer: 2
Должен вернуться:
PoweredDevice: 3
Scanner: 1
Printer: 2
Когда я слежу за выполнением с использованием GDB (7.11.1), это показывает, что промежуточные функции также вызывают PoweredDevice через список инициализации — но это следует игнорировать. Эта множественная инициализация PoweredDevice не приводит к неоднозначности каких-либо членов, но беспокоит меня, потому что код выполняется несколько раз, когда это должно происходить только один раз. Для более сложной задачи мне было бы неудобно использовать виртуальное наследование.
Почему эти промежуточные классы все еще инициализируют базу? Это извращение моего компилятора (gcc 5.4.0) или я неправильно понимаю, как работает виртуальное наследование?
Изменить: код
#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;
}
Похоже, это ошибка GCC, возникающая при использовании равномерной инициализации с виртуальным наследованием.
Если мы изменим:
Copier(int nScanner, int nPrinter, int nPower)
:Scanner {nScanner, nPower}, Printer {nPrinter, nPower}, PoweredDevice {nPower}
{ }
чтобы:
Copier(int nScanner, int nPrinter, int nPower)
:Scanner (nScanner, nPower), Printer (nPrinter, nPower), PoweredDevice (nPower)
{ }
Ошибка исчезает, и она ведет себя как ожидалось:
PoweredDevice: 3
Scanner: 1
Printer: 2
Print m_nPower: 3
3
И Clang, и Visual Studio способны правильно скомпилировать исходный код и дать ожидаемый результат.
Других решений пока нет …