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.
Спасибо за внимание.
This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.
Комментариев нет:
Отправить комментарий