|
Страница 1 из 3 Реализация библиотеки делегатов, которая может работать быстрее, чем "Наиболее быстрые делегаты 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 в качестве параметра
Конечно, это предполагает дополнительный вызов функции, но непроизводительные затраты существенно зависят от оптимизатора. Иногда их может и вовсе не быть.
|