Указатели функций-членов и наиболее быстрые делегаты C++ - Преобразование любых указателей функций-членов в стандартную форму

ОГЛАВЛЕНИЕ

Преобразование любых указателей функций-членов в стандартную форму

Ядро нашего кода – это класс, преобразующий произвольный указатель класса и произвольный указатель функции-члена в обобщенный указатель класса и в обобщенную функцию-член. C++ не имеет типа 'обобщенная функция-член', поэтому мы преобразовываем к функциям-членам неопределенного класса CGenericClass.

Многие компиляторы одинаково трактуют все указатели функций-членов независимо от класса. Для большинства из них непосредственный reinterpret_cast<> из данного указателя функции-члена в обобщенный указатель функции-члена будет работать. Если оно не работает, то компилятор не стандартный. Для остальных компиляторов (Microsoft Visual C++ и Intel C++) мы вынуждены преобразовывать указатели функций-членов множественного или виртуального наследования в указатели одиночного наследования. Это требует применения шаблонов и хитрых приемов. Учтите, что этот прием необходим только из-за того, что эти компиляторы не совместимы со стандартами, но есть и плюс: этот прием дает оптимальный код.

Так как мы знаем, как компилятор сохраняет указатели функций-членов внутри, и так как мы знаем, как указатель this нужно изменить для данной функции, мы можем сами изменить указатель this при определении делегата. Для указателей одиночного наследования изменения не требуются; для множественного наследования используется простое прибавление; а с виртуальным наследованием получается путаница. Но это работает, и в большинстве случаев вся работа выполняется во время компиляции!

Как мы можем различать различные виды наследования? Нет формального способа выяснить, использует класс множественное наследование или нет. Но есть хитрый способ, который можно увидеть, если посмотреть на представленную выше таблицу -- в MSVC каждый вид наследования порождает указатель функции-члена разного размера. Поэтому мы применяем специализацию шаблона, основанную на размере указателя функции-члена! Для множественного наследования это простой расчет. Аналогичные, но намного более неприятные вычисления используются в случае неизвестного наследования (16 байт).

Для Microsoft's (and Intel's) нестандартных 12-байтовых указателей виртуального наследования Microsoft (и Intel) используется еще один прием, основанный на идее, придуманной Джоном Длугозцом. Важная особенность указателей функций-членов Microsoft/Intel, которую мы использовали, состоит в том, что член CODEPTR всегда вызывается, независимо от значений других членов. (Это не верно для других компиляторов, например, GCC, который получает адрес функции из таблицы, если вызывается виртуальная функция.) Длугозц решил создать фальшивый указатель функции-члена, в котором codeptr ссылается на тестовую функцию, возвращающую указатель 'this', который был использован. Когда вы вызываете данную функцию, компилятор выполняет все вычисления за вас, используя скрытое значение vtordisp.

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

Существенное преимущество реализации делегатов с помощью нестандартного преобразования типов состоит в том, что их можно сравнить на предмет равенства. Большинство существующих реализаций делегатов не могут делать этого, что делает сложным их использование для определенных задач, таких как реализация групповых (многоадресных) делегатов [Sutter3].