Ответ 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. Я бы посоветовал кому-то не считать само собой разумеющимся, что указатель на базу будет реализован с точки зрения таблицы.