Почему vptr хранится как первая запись в памяти класса с виртуальными функциями?

Для некоторых компиляторов, если у класса есть виртуальные функции, тогда к его vptr можно получить доступ по адресу первого байта его объекта. Например,

class Base{
public:
virtual void f(){cout<<"f()"<<endl;};
virtual void g(){cout<<"g()"<<endl;};
virtual void h(){cout<<"h()"<<endl;};
};

int main()
{
Base b;

cout<<"Address of vtbl:"<<(int *)(&b)<<endl;

return 0;
}

Я знаю, что это зависит от поведения компилятора. Так как есть случай, когда vptr хранится как самая первая запись, в чем преимущество этого? Помогает ли это улучшить производительность или просто потому, что доступ к vbtl проще &б?

3

Решение

Это деталь реализации, но на самом деле многие реализации делают это.

Это довольно эффективно и удобно. Предположим, вам нужно вызвать виртуальную функцию для данного объекта. У вас есть указатель на этот объект и индекс виртуальной функции. Вам нужно как-то найти, какую функцию следует вызывать с этим индексом и для этого объекта. Хорошо, вы просто получаете доступ к первому sizeof(void*) байты за указателем и найдите, где находится vtable, затем получите доступ к необходимому элементу vtable, чтобы получить адрес функции.

Вы можете хранить отдельную карту «vtable для каждого объекта» или чего-то еще, но если вы решите, что хотите сохранить vptr внутри объекта, то логично использовать только первые байты, а не последние байты или любое другое место, потому что с этим Подход вы знаете, где найти vptr, как только у вас есть указатель на объект, никаких дополнительных данных не требуется.

4

Другие решения

Хотя это определенная реализация, кажется, нет большого выбора.

Прежде всего, мы можем видеть, что вы, эфир, должны иметь vptr или встроенный vtable, Последнее означает, что вам придется скопировать vtable на конструкции, и он потребляет больше памяти, но будет иметь преимущество, заключающееся в избежании разыменования одного указателя при каждом вызове метода. Вероятно, для них обоих есть веские аргументы в зависимости от ситуации — большинство реализаций предпочли уменьшить время создания и общее потребление памяти вместо экономии времени отправки.

Когда выбран vptr Подход мы видим, что мы должны поддерживать двоичную совместимость расположения базовых и производных классов. Прежде всего, мы можем достичь этого (часто), используя один vptr, этот vptr должны по соображениям совместимости жить в самом базовом классе.

При работе с простым наследованием наиболее простой способ преобразования между производным и базовым классом состоит в том, чтобы сохранить значение указателя, что будет означать, что компоновка должна быть в первую очередь полями для базового класса, за которыми следует добавление производных классов.

Теперь мы довольно близки к тому, почему vptr первый. Он просто должен быть рядом с началом объекта, так как он должен жить в самой основной части объекта.

Тогда по той причине, что мы поместили его в смещение 0, возможно, это согласованное смещение, доступное для всех классов. Вы просто не можете гарантировать, что есть какие-либо данные, которые могут быть размещены до vptr,

Ввод vptr при смещении 0 тоже есть некоторые преимущества. Если вы знаете объект, чтобы иметь vptr Вы знаете, что вам придется смотреть на смещение 0 без необходимости знать тип объекта (больше, чем он имеет vptr). Это может пригодиться для некоторых целей отладки ( vtable часто содержит достаточно информации, чтобы вывести фактический тип). Особенно это делает typeid и аналогичные проще реализовать, так как вам нужно только посмотреть на те же смещения, чтобы получить type_info узел через предопределенные смещения — это означает, что вы можете поделиться фактическим кодом для typeid,

2