Во-первых: без виртуальных функций, возможно, что нет классов vptr
. 8 байтов, которые вы видите, являются артефактом того, как реализовано виртуальное наследование.
Для нескольких классов в иерархии часто бывает одинаково vptr
. Для этого необходимо, чтобы их смещение в конечном классе было одинаковым, а для списка записей vtable в базовом классе была исходная последовательность списка записей vtable в производном классе .
Оба условия выполнены практически во всех реализациях для одиночного наследования.Независимо от того, насколько глубоким наследование, обычно будет только один vptr
, используемый всеми классами.
В случае множественного наследования, всегда будет по крайней мере один класс, для которого эти требования не выполняются, так как две базы классы не могут иметь общий начальный адрес, и если они не имеют точно одни и те же виртуальные функции, только одна vtable может быть исходной последовательностью .
Виртуальное наследование добавляет еще одну причуду, поскольку позиция виртуальной базы относительно наследуемого от нее класса будет варьироваться от в зависимости от остальной иерархии. В большинстве реализаций, которые я видел , используйте для этого отдельный указатель, хотя эту информацию можно также разместить в таблице .
Если мы возьмем иерархию, добавляя виртуальные функции, так что мы некоторые имеющие vptr
, мы замечаем, что B
и D
еще можно разделить vtable
, но оба A
и C
нуждаются в отдельном vtables
. Это означает, что , если у ваших классов были виртуальные функции, вам понадобится как минимум три vptr
. (Из этого я делаю вывод, что ваша реализация использует отдельных указателей на виртуальную базу. С B
и D
разделяющий же указатель, и C
со своим собственным указателем. И, конечно же, A
не имеют виртуальную базу, не нужен указатель на себя.)
Если вы пытаетесь проанализировать то, что происходит, я бы предложить добавить новую виртуальную функцию в каждом классе, и добавление указателя размера интегрального типа, который вы начинаете с другого известного значения для каждого класса . (Используйте конструкторы для установки значения.) Затем создайте экземпляр класса , возьмите его адрес и затем выведите адрес для каждого базового класса . А затем сбросьте класс: известные фиксированные значения помогут в идентифицировать, где лежат разные элементы. Что-то вроде:
struct VB
{
int vb;
VB() : vb(1) {}
virtual ~VB() {}
virtual void fvb() {}
};
struct Left : virtual VB
{
int left;
Left() : left(2) {}
virtual ~Left() {}
virtual void fvb() {}
virtual void fleft() {}
};
struct Right : virtual VB
{
int right;
Right() : right(3) {}
virtual ~Right() {}
virtual void fvb() {}
virtual void fright() {}
};
struct Derived : Left, Right
{
int derived;
Derived() : derived(5) {}
virtual ~Derived() {}
virtual void fvb() {}
virtual void fleft() {}
virtual void fright() {}
virtual void fderived() {}
};
Вы можете добавить Derived2
, который вытекает из Derived
и увидеть , что происходит с относительными адресами между, например, Left
и VB
в зависимости от того, имеет ли объект тип Derived
или Derived2
.
Вам нужно сказать, какой компилятор и архитектура вы используете. –
Ничто в стандарте не говорит о виртуальных указателях, которые они на самом деле не существуют. Таким образом, размер равен 8, потому что компилятору необходимо, чтобы он был 8. Это деталь реализации, и в ней мало смысла размышлять, так как это может быть другим в другом компиляторе. –