Вопрос о множественном наследовании, виртуальных базовых классах и размере объектов в C ++ - PullRequest
12 голосов
/ 28 декабря 2008

Следующий код печатает 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?

Ответы [ 3 ]

20 голосов
/ 28 декабря 2008

Давайте посмотрим на схему классов двух корпусов.

Без виртуального у вас есть два базовых класса ("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 платит за это (редактирует) прямой (/ редактирует) просмотр, так как виртуальных функций нет.

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

Мы можем ожидать, что все компиляторы сделают это, но, возможно, нет. На ARM стр. 225 обсуждаются виртуальные базовые классы без упоминания vtables. На странице 235 конкретно рассматриваются «виртуальные базовые классы с виртуальными функциями» и имеется схема, показывающая схему памяти, в которой есть указатели из частей X и Y, которые отделены от указателей на виртуальную таблицу. Я бы посоветовал никому не принимать как должное, что указатель на Base будет реализован в терминах таблицы.

9 голосов
/ 28 декабря 2008

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

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 голосов
/ 28 декабря 2008

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

...