C++ 3d.Комментарии




Указатели на члены - часть 3


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

Указатели на невиртуальные функции члены являются или просто указателем на функцию, или содержат такой указатель в качестве одного из фрагментов. Очевидно, что их передавать в другое адресное пространство нельзя. Впрочем, в этом также нет ничего неожиданного.

А теперь самое интересное -- указатели на виртуальные функции-члены. Как вы можете видеть, только у одного из трех компиляторов они получились похожими на "передаваемые" -- у второго.

Итак, указатели на виртуальные функции-члены можно безопасно передавать в другое адресное пространство чрезвычайно редко. И это правильно! Дело в том, что в определение C++ закралась ошибка: указатели на обычные и виртуальные члены должны быть разными типами. Только в этом случае можно обеспечить оптимальность реализации.

Указатели на функции-члены во втором компиляторе реализованы неоптимально, т.к. иногда они содержат указатель на "обычную" функцию (ffff0000 004014e4), а иногда -- индекс виртуальной функции (00020000 00000008). В результате чего, вместо того, чтобы сразу произвести косвенный вызов функции, компилятор проверяет старшую часть первого int, и если там стоит -1 (ffff), то он имеет дело с обычной функцией членом, иначе -- с виртуальной. Подобного рода проверки при каждом вызове функции-члена через указатель вызывают ненужные накладные расходы.

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

Например, следующий код: struct S { virtual void vf() { /* 1 */ } void f () { /* 2 */ } };

void g(void (S::*fptr)(), S* sptr) { (sptr->*fptr)(); }

int main() { S s; g(S::vf, &s); g(S::f , &s); }

превращается в псевдокод: void S_vf(S *const this) { /* 1 */ } void S_f (S *const this) { /* 2 */ }

void S_vf_stub(S *const this) { // виртуальный вызов функции S::vf() (this->vptr[index_of_vf])(this); }

void g(void (*fptr)(S *const), S* sptr) { fptr(sptr); }

int main() { S s; g(S_vf_stub, &s); // обратите внимание: не S_vf !!! g(S_f , &s); }

А если бы в C++ присутствовал отдельный тип "указатель на виртуальную функцию-член", он был бы представлен простым индексом виртуальной функции, т.е. фактически простым size_t, и генерации функций-заглушек (со всеми вытекающими потерями производительности) было бы можно избежать. Более того, его, как и указатель на данные-член, всегда можно было бы передавать в другое адресное пространство.




Содержание  Назад  Вперед