2015-11-06 12 views
2

Для некоторых компиляторов, если класс имеет виртуальные функции, то его 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, используя & b?

+0

Является ли поведение «vptr, доступным по первому байту объекта», различным для ОС? –

+1

Это зависит от реализации. –

+2

* «Мы знаем, что если класс имеет виртуальные функции, то его vptr можно получить с адресом первого байта своего объекта« *. Нет, нет. –

ответ

4

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

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

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

2

Несмотря на то, что это определенная реализация, похоже, не является реальным выбором.

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

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

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

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

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

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