Играя с наследованием, мне довелось попробовать это:
class A
{ int i; };
class B : virtual public A
{ int j; };
class C : public B
{ int k; };
int main()
{
std::cout<<sizeof(C)/sizeof(int);
return 0;
}
Который дал мне выход 6
В то время как следующее работало как ожидалось, давая вывод 3
class A
{ int i; };
class B : public A // No virtual here
{ int j; };
class C : public B
{ int k; };
int main()
{
std::cout<<sizeof(C)/sizeof(int);
return 0;
}
Почему эта разница? и почему это вдвое больше, чем во втором случае?
class A {
int i;
};
class B : public A {
int j;
};
В этом примере, который делает не использовать виртуальное наследование, объект типа B
можно выложить так, как будто B
был определен так:
class B0 {
int i;
int j;
};
Как только вы вводите виртуальное наследование, это не работает:
class C : public virtual A {
int k;
};
class D : public virtual A {
int l;
};
class E : public C, public D {
int m;
};
Объект типа C
имеет два int
Участники: k
из определения C
а также i
из определения A
, Точно так же объект типа D
имеет два int
Участники, l
а также i
, Все идет нормально. Сложная часть приходит с классом E
: оно тоже имеет один int
член i
потому что оба случая A
являются виртуальными базами. Так что ни C
ни D
не может быть написано как B0
выше, потому что тогда E
в конечном итоге два копии i
,
Решение состоит в том, чтобы добавить слой косвенности. Объекты типа C
, D
, а также E
выглядеть примерно так (псевдокод, не пытайтесь его скомпилировать):
class C0 {
int *cip = &i;
int k;
int i;
};
class D0 {
int *dip = &i;
int l;
int i;
};
class E0 {
// C0 subobect:
int *cip = &i;
int k;
// D0 subobject:
int *dip = &i;
int l;
// E data:
int *eip = &i;
int m;
int i;
};
То, что вы видите в размере E
это те дополнительные указатели, которые позволяют иметь одну копию i
независимо от того, как C
а также D
объединены в производный класс. (На самом деле, каждый из этих указателей будет указателем на A
, поскольку A
может, конечно, иметь более одного члена данных, но это слишком сложно представить в этом простом псевдокоде).
Это зависит от реализации.
Однако почти все компиляторы будут использовать один и тот же механизм всякий раз, когда у вас есть virtual
Ключевое слово, компилятор должен сделать некоторую дополнительную бухгалтерию через vptr
а также vtables
, Эта дополнительная бухгалтерия добавляет к размеру класса.
Строго говоря, вы должны полагаться на размер, чтобы быть чем-то конкретным, и именно поэтому стандарт предоставляет sizeof
чтобы получить реальный размер, а не угадывать его.
Проще говоря, виртуальное наследование требует дополнительных затрат. Типичная реализация потребует как минимум дополнительного указателя.
Смотрите вопрос 4 в Виртуальные таблицы и виртуальные указатели для множественного виртуального наследования и приведения типов и ответы.
Это зависит от реализации вашего компилятора. Разный компилятор имеет разный результат. Но одно точно, результата должно быть больше трех.