Первое: без виртуальных функций вполне вероятно, что в классах нет vptr
.8 байтов, которые вы видите, являются артефактом реализации виртуального наследования.
Часто несколько классов в иерархии могут совместно использовать один и тот же vptr
.Чтобы это произошло, необходимо, чтобы их смещение в конечном классе было одинаковым, а список записей vtable в базовом классе должен быть начальной последовательностью - список записей vtable в производном классе.
Оба условия соблюдаются практически во всех реализациях для одиночного наследования.Независимо от того, насколько глубоко наследуется, обычно будет только один vptr
, общий для всех классов.
В случае множественного наследования всегда будет хотя бы один класс, для которого эти требования не выполняются.не выполнено, поскольку два базовых класса не могут иметь общий начальный адрес, и, если они не имеют абсолютно одинаковые виртуальные функции, только одна таблица vtable может быть начальной последовательностью другого.
Виртуальное наследование добавляетеще одна странность, поскольку положение виртуальной базы относительно класса, наследующего ее, будет варьироваться в зависимости от остальной части иерархии.В большинстве реализаций, которые я видел, для этого используется отдельный указатель, хотя должна быть также возможность поместить эту информацию в 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
.