Указатели функций-членов и наиболее быстрые делегаты 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'.