Подтвердить что ты не робот

Вопрос о множественном наследовании, виртуальных базовых классах и размере объекта в С++

Следующий код печатает 20, то есть sizeof (z) равен 20.

#include <iostream.h>
class Base
{
      public:
            int a;
};

class X:virtual public Base
{
      public:
            int x;
};

class Y:virtual public Base
{
      public:
            int y;
};

class Z:public X,public Y
{
};

int main()
{
Z z;
cout << sizeof(z) <<endl;
}

Если я не использую здесь виртуальные базовые классы, то есть для следующего кода: sizeof (z) равен 16.

#include <iostream.h>
class Base
{
      public:
            int a;
};

class X:public Base
{
      public:
            int x;
};

class Y:public Base
{
      public:
            int y;
};

class Z:public X,public Y
{
};

int main()
{
Z z;
cout << sizeof(z) <<endl;
}

Почему sizeof (z) больше (20) в первом случае? Не должно быть 12, поскольку база будет включена только один раз в Z?

4b9b3361

Ответ 1

Посмотрите на макет класса этих двух случаев.

Без виртуального, у вас есть два базовых класса ( "X" и "Y" ) с целым числом каждый, и каждый из этих классов интегрирует в них базовый класс "Base", который также имеет целое число. Это 4 целых числа, 32 бита каждая, общая сумма ваших 16 байтов.

Offset  Size  Type  Scope  Name
     0     4   int   Base     a
     4     4   int      X     x
     8     4   int   Base     a
    12     4   int      Y     y
    16 size (Z members would come at the end)

(Edit: я написал программу в DJGPP, чтобы получить макет и подстроил таблицу для его учета.)

Теперь расскажем о виртуальных базовых классах: они заменяют фактический экземпляр класса указателем на общий экземпляр. В вашем классе "Z" есть только один "базовый" класс, и оба экземпляра "X" и "Y" указывают на него. Поэтому у вас есть целые числа в X, Y и Z, но у вас есть только один Z. Это означает, что у вас есть три целых числа или 12 байтов. Но X и Y также имеют указатель на общий Z (иначе они не знали, где его найти). На 32-битной машине два указателя добавят еще 8 байтов. Это составляет 20, которые вы видите. Макет памяти может выглядеть примерно так (я не проверял его... у ARM есть пример, где упорядочение - это X, Y, Z, а затем Base):

Offset  Size        Type  Scope  Name  Value (sort of)
     0     4 Base offset      X     ?  16 (or ptr to vtable)
     4     4         int      X     x
     8     4 Base offset      Y     ?  16 (or ptr to vtable)
    12     4         int      Y     y
    16     4         int   Base     a
    20 size (Z members would come before the Base)

Таким образом, разность памяти представляет собой комбинацию из двух вещей: один меньше целых и еще два указателя. В отличие от другого ответа, я не считаю, что vtables платят за любой (редактировать) прямой (/edit) рулон в этом, поскольку нет виртуальных функций.

Изменить: ppinsider предоставил больше информации о случае gcc, в котором он демонстрирует, что gcc реализует указатель на виртуальный базовый класс, используя пустую виртуальную таблицу (т.е. виртуальные функции). Таким образом, если бы существовали виртуальные функции, для экземпляра класса не требовался бы дополнительный указатель, требующий большего объема памяти. Я подозреваю, что обратная сторона является дополнительной косвенностью, чтобы добраться до базового класса.

Мы могли бы ожидать, что все компиляторы сделают это, но, возможно, нет. ARM страница 225 обсуждает виртуальные базовые классы без упоминания vtables. В частности, он обращается к "виртуальным базовым классам с виртуальными функциями" и имеет диаграмму, обозначающую макет памяти, где есть указатели из частей X и Y, которые отделены от указателей на vtable. Я бы посоветовал кому-то не считать само собой разумеющимся, что указатель на базу будет реализован с точки зрения таблицы.

Ответ 2

Ответ Марка Сансесона в значительной степени зависит от денег, но утверждение о том, что нет vtables, неверно. Вы можете использовать g++ -fdump-class-hierarchy, чтобы показать, что происходит. Здесь нет виртуального случая:

Class Base
   size=4 align=4
   base size=4 base align=4
Base (0x19a8400) 0

Class X
   size=8 align=4
   base size=8 base align=4
X (0x19a8440) 0
  Base (0x19a8480) 0

Class Y
   size=8 align=4
   base size=8 base align=4
Y (0x19a84c0) 0
  Base (0x19a8500) 0

Class Z
   size=16 align=4
   base size=16 base align=4
Z (0x19b1800) 0
  X (0x19a8540) 0
    Base (0x19a8580) 0
  Y (0x19a85c0) 8
    Base (0x19a8600) 8

Обратите особое внимание на аргумент "базовый размер". Теперь случай виртуальных и показывая только Z:

Class Z
   size=20 align=4
   base size=16 base align=4
Z (0x19b3000) 0
    vptridx=0u vptr=((& Z::_ZTV1Z) + 12u)
  X (0x19a8840) 0
      primary-for Z (0x19b3000)
      subvttidx=4u
    Base (0x19a8880) 16 virtual
        vbaseoffset=-0x0000000000000000c
  Y (0x19a88c0) 8
      subvttidx=8u vptridx=12u vptr=((& Z::_ZTV1Z) + 24u)
    Base (0x19a8880) alternative-path

Обратите внимание, что "базовый размер" один и тот же, но "размер" - это один указатель, и обратите внимание, что теперь есть указатель vtable! Это в свою очередь содержит конструкцию vtables для родительских классов и все межклассовые магии (строительная таблица vtables и таблица виртуальных таблиц (VTT)), как описано здесь:

http://www.cse.wustl.edu/~mdeters/seminar/fall2005/mi.html

Обратите внимание, что фактическая отправка функции vtable будет пустой.

Ответ 3

Дополнительный размер, вероятно, объясняется дополнительными VTables (http://en.wikipedia.org/wiki/Vtable), выделенными виртуальными классами и множественным наследованием.