Указатели функций-членов и наиболее быстрые делегаты C++ - Стабильные компиляторы
ОГЛАВЛЕНИЕ
Стабильные компиляторы
Почти во всех компиляторах два поля, названные delta и vindex, используются для преобразования исходного указателя this к adjustedthis, который передается функции. Например, здесь приведен метод, используемый в Watcom C++ и в Borland:
struct BorlandMFP { // также используется в Watcom
CODEPTR m_func_address;
int delta;
int vindex; // или 0, если нет виртуального наследования
};
if (vindex==0) adjustedthis = this + delta;
else adjustedthis = *(this + vindex -1) + delta
CALL funcadr
Если применяются виртуальные функции, то указатель функции ссылается на переходник из двух инструкций, чтобы определить реальную функцию, которую нужно вызвать. Borland применяет оптимизацию: если он знает, что класс использует только единичное наследование, он знает, что delta и vindex всегда будут нулевыми, поэтому он может пропустить вычисления в данном самом распространенном случае. Что важно, он изменяет только вычисление вызова, но не меняет саму структуру.
Многие другие компиляторы используют такое же вычисление, часто с небольшим переупорядочиванием структуры.
// Metrowerks CodeWarrior использует немного иной вариант.
// Он использует эту структуру даже в режиме Встроенного C++, в котором
// множественное наследование не допускается!
struct MetrowerksMFP {
int delta;
int vindex; // или -1, если нет виртуального наследования
CODEPTR func_address;
};
// Ранняя версия SunCC, вероятно, использовала другое упорядочивание:
struct {
int vindex; // или 0, если невиртуальное наследование
CODEPTR func_address; // или 0, если виртуальная функция
int delta;
};
Metrowerks не встраивает вычисления. Вместо этого они выполняются в короткой процедуре вызова функции-члена. Это немного уменьшает размер кода, но замедляет работу указателей функций-членов.
Digital Mars C++ (раньше Zortech C++, затем Symantec C++) использует различную оптимизацию. Для классов с единичным наследованием указатель функции-члена является просто адресом функции. Когда применяется более сложное наследование, указатель функции-члена ссылается на функцию-переходник, выполняющую необходимые изменения указателя this, и затем вызывает реальную функцию-член. Одна из этих небольших функций-переходников создается для каждой функции-члена, участвующей во множественном наследовании.
struct DigitalMarsMFP { // Почему все не делают это таким способом?
CODEPTR func_address;
};
Текущие версии компилятора GNU используют странную и запутанную оптимизацию. При виртуальном наследовании вы должны смотреть в таблицу, чтобы получить смещение voffset, требуемое для вычисления указателя this. В то время как вы делаете это, вы также можете сохранить указатель функции в таблице. С помощью этого поля m_func_address и m_vtable_index объединяются в одно, и их можно различить, убедившись, что указатели функций всегда указывают на четные адреса, но индексы таблицы всегда нечетные:
// GNU g++ использует запутанную оптимизацию пространства, также применяемую в IBM VisualAge и XLC.
struct GnuMFP {
union {
CODEPTR funcadr; // всегда четные
int vtable_index_2; // = vindex*2+1, всегда нечетные
};
int delta;
};
adjustedthis = this + delta
if (funcadr & 1) CALL (* ( *delta + (vindex+1)/2) + 4)
else CALL funcadr
Метод G++ хорошо документирован, поэтому он был приянт многими другими производителями, включая компиляторы IBM VisualAge и XLC, новые версии 64-битных компиляторов Open64, Pathscale EKO и Metrowerks. Упрощенная схема, используемая ранними версиями GCC, также широко распространена. SGI теперь снял с производства компиляторы MIPSPro и Pro64, и старый компилятор Apple MrCpp, применявшие этот метод. (Учтите, что компилятор Pro64 стал компилятором Open64 с открытым исходным кодом).
struct Pro64MFP {
short delta;
short vindex;
union {
CODEPTR funcadr; // если vindex==-1
short __delta2;
} __funcadr_or_delta2;
};
// если vindex==0, то это пустой указатель на член.
Компиляторы, основанные на синтаксическом анализаторе группы проектировщиков Edison (Comeau, Portland Group, Greenhills), используют почти аналогичный метод. Их вычисление таково (32-битные компиляторы PGI):
// Компиляторы, использующие синтаксический анализатор EDG (Comeau, Portland Group, Greenhills, и т.д.)
struct EdisonMFP{
short delta;
short vindex;
union {
CODEPTR funcadr; // если vindex=0
long vtordisp; // если vindex!=0
};
};
if (vindex==0) {
adjustedthis=this + delta;
CALL funcadr;
} else {
adjustedthis = this+delta + *(*(this+delta+vtordisp) + vindex*8);
CALL *(*(this+delta+funcadr)+vindex*8 + 4);
};
Многие компиляторы не разрешают множественное наследование для множественных систем. Таким образом эти компиляторы избегают всех странностей: указатель функции-члена – это обычный указатель функции со скрытым параметром 'this'.