Что происходит с виртуальным базовым классом при наследовании в многоуровневом наследовании?

Играя с наследованием, мне довелось попробовать это:

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;
}

Почему эта разница? и почему это вдвое больше, чем во втором случае?

1

Решение

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 может, конечно, иметь более одного члена данных, но это слишком сложно представить в этом простом псевдокоде).

1

Другие решения

Это зависит от реализации.

Однако почти все компиляторы будут использовать один и тот же механизм всякий раз, когда у вас есть virtual Ключевое слово, компилятор должен сделать некоторую дополнительную бухгалтерию через vptr а также vtables, Эта дополнительная бухгалтерия добавляет к размеру класса.

Строго говоря, вы должны полагаться на размер, чтобы быть чем-то конкретным, и именно поэтому стандарт предоставляет sizeof чтобы получить реальный размер, а не угадывать его.

2

Проще говоря, виртуальное наследование требует дополнительных затрат. Типичная реализация потребует как минимум дополнительного указателя.

Смотрите вопрос 4 в Виртуальные таблицы и виртуальные указатели для множественного виртуального наследования и приведения типов и ответы.

2

Это зависит от реализации вашего компилятора. Разный компилятор имеет разный результат. Но одно точно, результата должно быть больше трех.

0
По вопросам рекламы [email protected]