В моем коде есть базовый ромбовидный узор:
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
Когда у вас есть виртуальное наследование, существует только один подобъект общего базового класса для всех, который является производным виртуально от этого базового класса. И этот подобъект находится в самом производном классе. Где еще ему жить? На самом деле нет другого места, где вы всегда могли бы выразить это в четко определенной манере. Вы можете посмотреть stackoverflow.com/questions/6461784/…
@MichaelKenzel Я понимаю, что существует только один экземпляр общего базового класса, однако я ожидал, что он будет жить в классе Joined
, поскольку этот класс явно вызывает конструктор базового класса, аналогично невиртуальному наследованию.
А что, если есть другой базовый класс, который также вызывает конструктор виртуального базового класса в своем конструкторе?
Я понимаю. Значит, нет возможности сделать это красиво?
В любом случае, я был бы благодарен, если бы вы опубликовали это как ответ, и я могу принять его.
Это очень хороший вопрос, и было бы неплохо, если бы кто-нибудь мог объяснить Зачем, это сделано в C++ таким образом, вместо того, чтобы просто заявить, что самый производный класс должен инициализировать виртуальную базу.
Это указано в Инициализация баз и членов [class.base.init] (12.6.2 в проекте n4567). Мы можем прочитать в § 13 (подчеркните мой):
(13) In a non-delegating constructor, initialization proceeds in the following order:
(13.1) — First, and only for the constructor of the most derived class (1.8), virtual base classes are initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where “left-to-right” is the order of appearance of the base classes in the derived class base-specifier-list.
(13.2) — Then, direct base classes are initialized in declaration order as they appear in the base-specifier-list (regardless of the order of the mem-initializers).
(13.3) — Then, non-static data members are initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).
(13.4) — Finally, the compound-statement of the constructor body is executed.
[ Note: The declaration order is mandated to ensure that base and member subobjects are destroyed in the reverse order of initialization. —end note ]
Это означает, что виртуальный базовый класс будет инициализирован до при инициализации Joined
. Таким образом, в DerivedJoinedA
он по умолчанию инициализируется с value
, равным 0. Затем при инициализации Joined
инициализация CommonBase
игнорируется, потому что он уже был инициализирован, и value
сохраняет свое значение 0.
Вот почему вам нужно инициализировать виртуальные базовые классы в самом производном классе.
Спасибо за подробное объяснение. Объяснение со ссылкой на стандарт было тем, на что я надеялся!
Возможный дубликат Понимание виртуальных базовых классов и вызовов конструкторов