Почему виртуальные базовые классы должны быть построены с помощью самого производного класса?
Следующий код не будет компилироваться:
class A {
public:
A(int) {}
};
class B: virtual public A {
public:
B(): A(0) {}
};
// most derived class
class C: public B {
public:
C() {} // wrong!!!
};
Если я вызываю A
конструктор в C
список инициализации конструктора, то есть:
// most derived class
class C: public B {
public:
C(): A(0) {} // OK!!!
};
он работает.
По-видимому, причина в том, что виртуальные базовые классы всегда должны быть построены с помощью большинства производных классов.
Я не понимаю причину этого ограничения.
Ответы
Ответ 1
Потому что он избегает этого:
class A {
public:
A(int) {}
};
class B0: virtual public A {
public:
B0(): A(0) {}
};
class B1: virtual public A {
public:
B1(): A(1) {}
};
class C: public B0, public B1 {
public:
C() {} // How is A constructed? A(0) from B0 or A(1) from B1?
};
Ответ 2
Поскольку в иерархии классов, имеющей фактически унаследованный базовый класс, базовый класс будет/может делиться несколькими классами (например, в наследовании алмаза, где один и тот же базовый класс унаследован несколькими классами). Это означает, что будет только одна копия фактически унаследованного базового класса. По сути, это означает, что базовый класс должен быть построен первым. В конечном итоге это означает, что производный класс должен создать экземпляр заданного базового класса.
Например:
class A;
class B1 : virtual A;
class B2 : virtual A;
class C: B1,B2 // A is shared, and would have one copy only.
Ответ 3
Я нахожу это правило ошибочным и громоздким (но тогда какая часть множественного наследования не является?).
Но логически наложенный порядок построения должен отличаться от случая нормального (не виртуального) наследования. Рассмотрим пример Ajay, минус virtual:
class A;
class B1 : A;
class B2 : A;
class C: B1,B2
В этом случае для каждого C два As строятся, один как часть конструкции B1, другой - как часть конструкции B2. Код класса B отвечает за это и может это сделать. Порядок событий:
Start C ctor
Start B1 ctor
A ctor (in B ctor code)
End B1 ctor
Start B2 ctor
A ctor (in B ctor code)
End B2 ctor
End C ctor
Теперь рассмотрим виртуальное наследование в
class A;
class B1 : virtual A;
class B2 : virtual A;
class C: B1,B2
Один порядок события
Start C ctor
Start B1 ctor
// NO A ctor
End B1 ctor
Start B2 ctor
// NO A ctor
End B2 ctor
A ctor // not B code!
End C ctor
Возможно, что A построено до B1 и B2, но это не имеет значения. Важно то, что конструкция A не происходит при построении других базовых классов. Этот код просто не выполняется и не может быть выполнен, потому что фактически унаследованный под-объект базового класса типа A является частью и под управлением самого производного класса, который недоступен из кода B1 и B2; действительно, C даже не полностью сконструирован в момент времени B1 или B2, и он может попытаться создать A в C.