Завершить Виртуальное наследование
В моем коде у меня есть базовый ромбовидный узор:
CommonBase
/ \
/ \
DerivedA DerivedB
\ /
\ /
Joined
Это реализовано следующим образом: общий базовый класс имеет конструктор по умолчанию и конструктор, принимающий параметр:
struct CommonBase {
CommonBase() : CommonBase(0) {}
CommonBase(int val) : value(val) {}
const int value;
};
struct DerivedA : public virtual CommonBase {
void printValue() {
std::cout << "The value is " << value << "\n";
}
};
struct DerivedB : public virtual CommonBase {
void printValueTimes2() {
std::cout << "value * 2 is " << value * 2 << "\n";
}
};
struct Joined : public DerivedA,
public DerivedB {
Joined(int val) : CommonBase(val) {
std::cout << "Constructor value is " << val << "\n";
std::cout << "Actual value is " << value << "\n";
}
};
Joined
Класс инициализирует виртуальную базу с помощью конструктора, который принимает параметр, и все работает как положено.
Тем не менее, когда я выведу класс из Joined
класс, что-то странное происходит - CommonBase
конструктор по умолчанию вызывается, если я явно не инициализирую CommonBase
в конструкторе производных классов.
Это демонстрируется с помощью этого кода:
struct JoinedDerivedA : public Joined {
JoinedDerivedA() : Joined(99) {
printValue();
}
};
struct JoinedDerivedB : public Joined {
JoinedDerivedB() : Joined(99), CommonBase(99) {
printValue();
}
};
int main() {
std::cout << "======= Joined =======\n";
Joined j(99);
j.printValue();
std::cout << "\n=== JoinedDerivedA ===\n";
JoinedDerivedA a;
std::cout << "\n=== JoinedDerivedB ===\n";
JoinedDerivedB b;
return 0;
}
Выход этого кода
======= Joined =======
Constructor value is 99
Actual value is 99
The value is 99
=== JoinedDerivedA ===
Constructor value is 99
Actual value is 0 // <-- unexpected behaviour
The value is 0
=== JoinedDerivedB ===
Constructor value is 99
Actual value is 99
The value is 99
Почему это так? Можно ли не выполнять явную инициализацию общего базового класса в производных классах снова?
Вот код для ideone, поэтому вы можете запустить его самостоятельно: https://ideone.com/Ie94kb
1 ответ
Это указано в разделе "Инициализация баз и членов" [class.base.init] (12.6.2 в черновике n4567). Мы можем прочитать в §13 (подчеркните мой):
(13) В не делегирующем конструкторе инициализация происходит в следующем порядке:
(13.1) - Во-первых, и только для конструктора самого производного класса (1.8), виртуальные базовые классы инициализируются в том порядке, в котором они отображаются при обходе слева направо по глубине направленного ациклического графа базовых классов, где "слева направо" - порядок появления базовых классов в списке базовых спецификаторов производного класса.
(13.2) - Затем прямые базовые классы инициализируются в порядке объявления, как они появляются в списке базовых спецификаторов (независимо от порядка mem-initializer).
(13.3) - Затем не статические члены-данные инициализируются в том порядке, в котором они были объявлены в определении класса (опять же, независимо от порядка mem-инициализаторов).
(13.4) - Наконец, составной оператор тела конструктора выполняется.
[Примечание. Порядок декларирования должен обеспечивать уничтожение базовых и дочерних подобъектов в обратном порядке инициализации. —Конечная записка]
Это означает, что виртуальный базовый класс будет инициализирован до инициализации Joined
, Так в DerivedJoinedA
, по умолчанию инициализируется с value
0. Затем при инициализации Joined
инициализация CommonBase
игнорируется, потому что он уже был инициализирован и value
сохраняет значение 0
Вот почему вы должны инициализировать виртуальные базовые классы в наиболее производном классе.