Это все реализация определена. Я использую VC10 Beta2. Ключ к пониманию этой вещи (реализация виртуальных функций), вам нужно знать о секретном переключателе в компиляторе Visual Studio, / d1reportSingleClassLayoutXXX . Я вернусь к этому через секунду.
Основное правило - виртуальная таблица должна находиться со смещением 0 для любого указателя на объект. Это подразумевает наличие нескольких таблиц для множественного наследования.
Пара вопросов здесь, я начну сверху:
Означает ли это, что существует только один vptr, даже если у класса B и класса A есть виртуальная функция? Почему там только один вптр?
Вот так работают виртуальные функции, вы хотите, чтобы базовый класс и производный класс совместно использовали один и тот же указатель vtable (указывающий на реализацию в производном классе.
Кажется, что в этом случае два vptrs находятся в макете ..... Как это происходит? Я думаю, что два vptrs один для класса A и другой для класса B .... так что нет vptr для виртуальной функции класса C?
Это макет класса C, как сообщает / d1reportSingleClassLayoutC:
class C size(20):
+---
| +--- (base class A)
0 | | {vfptr}
4 | | a
| +---
| +--- (base class B)
8 | | {vfptr}
12 | | b
| +---
16 | c
+---
Вы правы, есть две таблицы, по одной для каждого базового класса. Вот как это работает в множественном наследовании; если C * приведен к B *, значение указателя корректируется на 8 байтов. Для работы вызовов виртуальных функций виртуальная таблица все еще должна находиться со смещением 0.
vtable в приведенном выше макете для класса A рассматривается как vtable класса C (при вызове через C *).
Размер B составляет 16 байт. -------------- Без виртуального должно быть 4 + 4 + 4 = 12. Почему здесь есть еще 4 байта? Какой макет класса B?
Это макет класса B в этом примере:
class B size(20):
+---
0 | {vfptr}
4 | {vbptr}
8 | b
+---
+--- (virtual base A)
12 | {vfptr}
16 | a
+---
Как видите, есть дополнительный указатель для обработки виртуального наследования. Виртуальное наследование сложно.
Размер D составляет 32 байта -------------- это должно быть 16 (класс B) + 12 (класс C) + 4 (int d) = 32. Это правильно?
Нет, 36 байтов. То же самое касается виртуального наследования. Расположение D в этом примере:
class D size(36):
+---
| +--- (base class B)
0 | | {vfptr}
4 | | {vbptr}
8 | | b
| +---
| +--- (base class C)
| | +--- (base class A)
12 | | | {vfptr}
16 | | | a
| | +---
20 | | c
| +---
24 | d
+---
+--- (virtual base A)
28 | {vfptr}
32 | a
+---
Мой вопрос: почему при применении виртуального наследования появляется дополнительное пространство?
Указатель виртуального базового класса, это сложно. Базовые классы «объединены» в виртуальном наследовании. Вместо того, чтобы базовый класс был встроен в класс, класс будет иметь указатель на объект базового класса в макете. Если у вас есть два базовых класса, использующих виртуальное наследование (иерархия классов «diamond»), они оба будут указывать на один и тот же виртуальный базовый класс в объекте, вместо того, чтобы иметь отдельную копию этого базового класса.
Какое нижнее правило для размера объекта в этом случае?
Важный момент; нет никаких правил: компилятор может делать все, что ему нужно.
И последняя деталь; чтобы сделать все эти схемы компоновки классов, с которыми я компилирую:
cl test.cpp /d1reportSingleClassLayoutXXX
Где XXX - это совпадение подстроки структур / классов, которые вы хотите видеть в макете. Используя это, вы можете самостоятельно изучить влияние различных схем наследования, а также почему / где добавлено заполнение и т. Д.