Невероятно быстрые делегаты C++

ОГЛАВЛЕНИЕ

Реализация библиотеки делегатов, которая может работать быстрее, чем "Наиболее быстрые делегаты C++ " и полностью совестима со стандартами C++.

Введение

Дон Клагстон предложил подход к делегатам (далее называемым FastDelegate), который требует такого же вызывающего кода, что и создаваемый для вызова с помощью указателя на член-функцию в простейшем случае (он описал, почему некоторые компиляторы порождают более сложный код для полиморфных классов и для классов с виртуальным наследованием). Он описал, почему многие другие популярные подходы оказались неэффективными. К сожалению, его подход основан на 'хитром трюке' (как он сказал). Он работает во многих популярных компиляторах, но несовместим со стандартами C++.

Кажется верным, что FastDelegate – это самый быстрый способ. Но мы полагаем, что это заявление требует доказательств, потому что современные оптимизирующие компиляторы C++ делают поразительные вещи. Мы уверены, что boost::function и другие делегаты, основанные на динамическом выделении памяти, являются медленными, но кто сказал, что нет других хороших подходов?

Мы собираемся предложить другой подход, который:

      а) быстрый;

      б) не использует динамическое выделение памяти;

      с) полностью совместим со стандартами C++.

Еще один подход к делегатам

Рассмотрим делегат, получающий один аргумент и не возвращающий никакого значения. Его можно определить следующим образом, используя предпочтительный синтаксис (как boost::function и FastDelegate, наша библиотека поддерживает предпочтительный и совместимый синтаксисы; более подробную информацию смотрите в документации):

delegate<void (int)>

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

class delegate
{
public:
    delegate()
        : object_ptr(0)
        , stub_ptr(0)
    {}

    template <class T, void (T::*TMethod)(int)>
    static delegate from_method(T* object_ptr)
    {
        delegate d;
        d.object_ptr = object_ptr;
        d.stub_ptr = &method_stub<T, TMethod>; // #1
        return d;
    }

    void operator()(int a1) const
    {
        return (*stub_ptr)(object_ptr, a1);
    }

private:
    typedef void (*stub_type)(void* object_ptr, int);

    void* object_ptr;
    stub_type stub_ptr;

    template <class T, void (T::*TMethod)(int)>
    static void method_stub(void* object_ptr, int a1)
    {
        T* p = static_cast<T*>(object_ptr);
        return (p->*TMethod)(a1); // #2
    }
};

Делегат состоит из нетипированного указателя на данные (так как делегат не должен зависеть от типа приемника) и указателя на функцию. Эта функция получает указатель на данные в качестве дополнительного параметра. Он преобразует указатель данных в указатель объекта ('void*', в отличие от указателей-членов может быть благополучно преобразован обратно в указатели объектов: [expr.static.cast], элемент 10) и вызывает требуемую функцию-член.

Когда вы создаете непустой делегат, вы неявно реализуете функцию-заглушку путем получения ее адреса (смотрите строку #1 выше). Это возможно, потому что стандарт C++ разрешает использовать указатель на член или указатель на функцию в качестве параметра шаблона ([temp.params], элемент 4):

SomeObject obj;
delegate d = delegate::from_member<SomeObject,
              &SomeObject::someMethod>(&obj);

Теперь 'd' содержит указатель на функцию-заглушку, связываемый с 'someMethod' во время компиляции. Хотя был задан указатель на член, вызов в строке #2 выполняется так же быстро, как и прямой вызов метода (потому что его значение известно во время компиляции).

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

d(10); // вызов SomeObject::someMethod
       // для obj и передача им 10 в качестве параметра

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