В вашем примере ожидаемый результат. Virtual inheritance
вступает в игру в случае, когда у вас есть класс с множественным наследованием, родительские классы которого также наследуются от одного и того же класса/типа (т. Е. «Проблема с алмазом»). В вашем примере ваши классы могут быть настроены практически наследовать (если необходимо в другом месте кода), но они не обязательно «фактически наследуются» на основе вашего примера, поскольку ни один из производных классов (B1/B2/C1/C2
) не делает больше, чем наследует напрямую от A
.
Для расширения, я подправил свой пример, чтобы объяснить немного больше:
#include <cstdio>
#define tracefunc printf(__FUNCTION__); printf("\r\n")
struct A
{
A() { tracefunc; }
virtual void write() { tracefunc; }
virtual void read() { tracefunc; }
};
struct B1 : public A
{
B1() { tracefunc; };
void read(){ tracefunc; }
};
struct C1 : public A
{
C1() { tracefunc; };
void write(){ tracefunc; }
};
struct B2 : virtual public A
{
B2() { tracefunc; };
void read(){ tracefunc; }
};
struct C2 : virtual public A
{
C2() { tracefunc; };
void write(){ tracefunc; }
};
// Z1 inherits from B1 and C1, both of which inherit from A; when a call is made to any
// of the base function (i.e. A::read or A::write) from the derived class, the call is
// ambiguous since B1 and C1 both have a 'copy' (i.e. vtable) for the A parent class.
struct Z1 : public B1, public C1
{
Z1() { tracefunc; }
};
// Z2 inherits from B2 and C2, both of which inherit from A virtually; note that Z2 doesn't
// need to inherit virtually from B2 or C2. Since B2 and C2 both virtual inherit from A, when
// they are constructed, only 1 copy of the base A class is made and the vtable pointer info
// is "shared" between the 2 base objects (B2 and C2), and the calls are no longer ambiguous
struct Z2 : public B2, public C2
{
Z2() { tracefunc; }
};
int _tmain(int argc, _TCHAR* argv[])
{
// gets 2 "copies" of the 'A' base since 'B1' and 'C1' don't virtually inherit from 'A'
Z1 z1;
// gets only 1 "copy" of 'A' base since 'B2' and 'C2' virtualy inherit from 'A' and thus "share" the vtable pointer to the 'A' base
Z2 z2;
z1.write(); // ambiguous call to write (which one is it .. B1::write() (since B1 inherits from A) or A::write() ?)
z1.read(); // ambiguous call to read (which one is it .. C1::read() (since C1 inherits from A) or A::read() ?)
z2.write(); // not ambiguous: z2.write() calls C2::write() since it's "virtually mapped" to/from A::write()
z2.read(); // not ambiguous: z2.read() calls B2::read() since it's "virtually mapped" to/from A::read()
return 0;
}
Хотя это может быть «очевидно» для нас, людей, которые называют мы намерены сделать в случае переменной z1
, поскольку B1
не имеет метода write
, я бы «ожидал» компилятор, чтобы выбрать метод C1::write
, но из-за того, как работает отображение объектов памяти, возникает проблема, поскольку базовая копия A
в объекте C1
может иметь другая информация (указатели/ссылки/ручки), чем копия базы A
в B1
obj ect (поскольку имеется технически 2 копии базы A
); поэтому вызов B1::read() { this->write(); }
может привести к неожиданному поведению (хотя и не определено).
Ключевое слово virtual
в спецификаторе базового класса делает его явным, что другие классы, которые фактически наследуются от одного и того же базового типа, должны получить только 1 копию базового типа.
Обратите внимание, что приведенный выше код не должен компилироваться с ошибками компилятора, объясняя неоднозначные вызовы для объекта z1
. Если закомментировать z1.write();
и z1.read();
строки вывода (для меня по крайней мере) имеет следующий вид:
A::A
B1::B1
A::A
C1::C1
Z1::Z1
A::A
B2::B2
C2::C2
Z2::Z2
C2::write
B2::read
Обратите внимание на 2 вызовы A
CTOR (A::A
) до Z1
конструируют, в то время как Z2
имеет только 1 вызов к конструктору A
.
Я рекомендую прочитать the following on virtual inheritance, поскольку он более подробно описывает некоторые другие подводные камни, чтобы принять к сведению (например, тот факт, что фактически унаследованные классы должны использовать список инициализации для создания вызовов класса ctor или что вам следует избегать используя приведения типа C при выполнении такого типа наследования).
Это также объясняет немного больше того, что вы изначально намекали на упорядочение конструктора/деструктора, а точнее, как упорядочивание выполняется при использовании нескольких виртуальных наследований.
Надеюсь, что это поможет немного разобраться.
Что вы ожидали? «Бабушка» всегда называется первым. Неважно, какое унаследование вы используете. Если было иначе, как низкоуровневые классы использовали данные родителя в своих конструкторах? – ixSci
Виртуальное наследование только делает разницу в случае множественного наследования. Он предоставляется для распределения некоторой структуры через граф наследования. –