std::function
.
Классы std::function и boost::function являются высокоуровневыми обертками над функциями и функциональными объектами. Объекты таких классов позволяют хранить и вызывать функции и функторы с заданной сигнатурой, что бывает удобно, например, при создании callback вызовов (например, мы можем регистрировать несколько обработчиков, и это могут быть как обычные функции, так и объекты с определенным оператором =)
Этого замечательного шаблонного класса давно не хватало нам. Однако, в погоне за универсальностью разработчикам стандартной библиотеки пришлось пойти на некоторые жертвы. Так как С++ используется в первую очередь там, где необходимо выжать максимальную скорость из железа, со больше всего разочаровывает любой overhead по скорости. В частности, к таким вещам можно отнести:
- При создании объекта
std::function
вызывается операторnew
, как следствие того, чтоstd::function
позволяет сохранять в себя функтор произвольного размера. std::function
имеет семантику копирования, которая, в принципе, редко когда бывает необходима в при действиях с функторами (в связи с появлением move-семантики), но стоит определенных ресурсов CPU.
Для этого, конечно же, нужно написать свой std::function
без этих фатальных недостатков =) К счастью, новые возможности С++11 позволяют нам это сделать без лишних телодвижений и обращений к специфичным возможностям компиляторов.
Что хочется?
- Избавиться от вызовов new при создании объекта функтора.
- Убрать семантику копирования, чтобы избежать возможного копирования объектов функторов.
- Синтаксиса близкого к
std::function
. - Понятного кода.
- Побаловаться с С++11.
Ну и как же это сделать?
Для начала рассмотрим простой способ реализовать делегат на С++, который я подчерпнул из статей Fastest Possible C++ Delegates и Lightweight Generic C++ Callbacks.
class function_t { public: template <typename U> fixed_function_t(U &&object) { typedef typename std::remove_reference<U>::type unref_type; m_object_ptr = new unref_type(object); m_method_ptr = &method_stub<unref_type>; } void operator()() const { if (m_method_ptr) { (*m_method_ptr)(m_object_ptr); } } private: void *m_object_ptr; typedef R (*method_type)(void *); method_type m_method_ptr; template <class T> static void method_stub(void *object_ptr) { static_cast<T *>(object_ptr)->operator()(); } };
Пока что закроем глаза на отсутствие вызова delete
, чтобы не загружать код излишней информацией.
А дальше, будем добавлять все необходимые на фичи.
Убираем выделение динамической памяти
Для этого будем конструировать объект делегата в уже существующем на момент создания нашего function
буфере. Добавим в наш класс следующее поле:
class function_t { enum {STORAGE_SIZE = 32}; public: // ... private: typename std::aligned_storage<STORAGE_SIZE, STORAGE_SIZE>::type m_storage; // ... };
И будем конструировать объект делегата в нём.
template <typename U> fixed_function_t(U &&object) { typedef typename std::remove_reference<U>::type unref_type; m_object_ptr = new (&m_storage) unref_type(std::forward<U>(object)); m_method_ptr = &method_stub<unref_type>; }
Убираем возможность копирования.
struct noncopyable_t { noncopyable_t & operator=(const noncopyable_t&) = delete; noncopyable_t(const noncopyable_t&) = delete; noncopyable_t() = default; }; class function_t : private noncopyable_t { //...
При этом надо не забыть реализовать правильно move семантику! В move конструкторе придется делать memcpy
для m_storage
, и изменить указатель на метод.
Добавляем синтаксиса близкий к std::function
template <typename R, typename... ARGS> class fixed_function_t : private noncopyable_t { enum {STORAGE_SIZE = 32}; public: //... R operator()(ARGS... args) const { if (m_method_ptr) { return (*m_method_ptr)(m_object_ptr, args...); } return R(); } private: //... template <class T> static R method_stub(void *object_ptr, ARGS... args) { return static_cast<T *>(object_ptr)->operator()(args...); } };
Готово!
Вместо заключения
Надеюсь мой велосипед будет для вас полезен в качестве источника идей, а, может быть, он даже попадет в каком-то виде в продакшн. Я буду только рад.
Полный код с тестами можно найти, как это принято, на Github.
Спасибо за внимание.
ссылка на оригинал статьи http://habrahabr.ru/post/203888/
Добавить комментарий