Дорвался вот до С++ 11 и придумалась такая штука... Есть некий асинхронный интерфейс, обрабатывающий запросы. С запросом надо связать callback. std::function отлично подходит, но встал вопрос оптимизации в плане выделения памяти. Конечно в студии, например, есть оптимизация хранения небольших функторов (хотя та ещё оптимизация, если рассчитывать на быстрый move). Или на худой конец custom аллокатор можно подсунуть (только вот gcc custom аллокаторы не умеет). Ну а callback'и то копеечные, thiscall какой нибудь плюс пара-тройка связанных ref/smartptr аргументов или та же лямбда с захватом this. Собственно почему бы не сляпать свой non-copyable/non-movable вариант со статическим хранилищем заданного размера? Размер можно примерно подобрать, исходя из используемых callback-функторов.
Здравствуйте, sokel, Вы писали:
S>Можно, кстати, сделать возможность move/copy параметром шаблона.
я бы посоветовал заменить long double на обычный double или на int64_t, чтобы гарантировать максимальное выравнивание (если я правильно понял назначение union) http://ideone.com/xolMTT
Здравствуйте, uzhas, Вы писали:
U>я бы посоветовал заменить long double на обычный double или на int64_t, чтобы гарантировать максимальное выравнивание (если я правильно понял назначение union) U>http://ideone.com/xolMTT
Ага, спасибо, а ещё лучше наверное на std::aligned_storage<MaxSize>.
[off]
Вот, блин! Читаю твой пост и понимаю, что несмотря на непрерывный десятилетний стаж С++ надо учить заново. Не ждать же, когда контора на новый стандарт переползёт.
[/off]
Здравствуйте, sokel, Вы писали:
S>aligned_storage вместо union + move/copy + параметризация сигнатурой (typedef stack_function<void(), 8>):
обложил нотами, может быть полезными, а могут быть и бесполезными
на абсолютную правоту не претендую
под катом, ибо много букв
Скрытый текст
#include <functional>
#ifdef _WIN32
#pragma warning( push )
#pragma warning( disable : 4521 4522 )
#endif
namespace details {
//note 0: для пущего перформанса можно было бы упразднить виртуальные вызовы. может серьезно ухужшить читабельность кода, но будет лучше с точки зрения производительности
//практически это было бы равносильно хранению виртуальной таблицы рядом с объектом, локализация кода улучшилась бы, ликвидировали бы один уровень индиректа имхо
//в любом случае подобный подход плохо инлайнится, к сожалению. обсуждали здесь : http://rsdn.ru/forum/cpp/5658888.flattemplate<typename Ret, typename ...Args>
class stack_function_interface {
typedef stack_function_interface<Ret, Args...> self_type;
public:
virtual Ret do_call(Args&& ...) = 0;
virtual ~stack_function_interface() {}
virtual self_type* copy_to(void*) const = 0;
virtual self_type* move_to(void*) = 0;
};
template<typename F, typename Ret, typename ...Args>
class stack_function_impl : public stack_function_interface<Ret, Args...>
{
typedef stack_function_interface<Ret, Args...> base;
public:
//note 1: здесь не универсальная ссылка, поэтому
// a) нужен конструктор из (F fn) или из (const F& fn)
// b) нужен std::move вместо std::forward
stack_function_impl(F&& fn)
: fn_(std::forward<F>(fn))
{}
stack_function_impl(const stack_function_impl& rhs)
: fn_(rhs.fn_)
{}
// note 2: ценность данных конструкторов (из неконстантных объектов) для всех классов из этого файла под вопросом (я бы попробовал их удалить)
stack_function_impl(stack_function_impl& rhs)
: fn_(rhs.fn_)
{}
//note 3: нужен std::move вместо std::forward
stack_function_impl(stack_function_impl&& rhs)
: fn_(std::forward<F>(rhs.fn_))
{}
Ret do_call(Args&& ... args) override final {
return fn_(std::forward<Args>(args)...);
}
base* copy_to(void* addr) const override final {
return new (addr)stack_function_impl(*this);
}
base* move_to(void* addr) override final {
return new (addr)stack_function_impl(std::move(*this));
}
private:
F fn_;
};
template<size_t MaxSize, typename Ret, typename ...Args>
class stack_function_base
{
public:
Ret operator()(Args&& ... args) {
if(!fn_)
throw std::bad_function_call();
return fn_->do_call(std::forward<Args>(args)...);
}
protected:
typedef stack_function_interface<Ret, Args...> fn_interface;
stack_function_base() : fn_(nullptr)
{}
template<typename F>
void assign_(F&& f) {
destroy_();
construct_(std::forward<F>(f));
}
~stack_function_base() { destroy_(); }
bool empty_() const {
return fn_ == nullptr;
}
void destroy_() {
if(fn_) {
fn_->~fn_interface();
//note 4: для однообразия использовать fn_ = nullptr;
fn_ = 0;
}
}
template<typename F>
void construct_(F&& f) {
typedef stack_function_impl<F, Ret, Args...> fn_type;
//note 5: неравенство можно сделать строгим
static_assert(sizeof(fn_type) <= MaxSize
, "non sufficient stack_function storage size");
fn_ = new (&fn_storage_) fn_type(std::forward<F>(f));
}
void move_construct_(stack_function_base&& rhs) {
if(rhs.fn_)
fn_ = rhs.fn_->move_to(&fn_storage_);
//note 6: я бы сделал rhs.destroy_() чтобы сбросить состояние у объекта, у которого мы все забрали. чаще всего так делают на практике имхо
}
void copy_construct_(const stack_function_base& rhs) {
if(rhs.fn_)
fn_ = rhs.fn_->copy_to(&fn_storage_);
}
private:
fn_interface* fn_;
using fn_storage_t = typename std::aligned_storage<MaxSize>::type;
fn_storage_t fn_storage_;
};
template<class Function, size_t MaxSize> struct get_stack_function_base;
template<size_t MaxSize, typename Ret, typename ...Args>
struct get_stack_function_base<Ret(Args...), MaxSize> {
typedef stack_function_base<MaxSize, Ret, Args...> type;
};
}
//note 7: не следует ли добавить typename после public ?template<typename Function, size_t MaxSize>
class stack_function : public details::get_stack_function_base<Function, MaxSize>::type
{
typedef typename details::get_stack_function_base<Function, MaxSize>::type base;
public:
//note 8: предположу, что достаточно оставить лишь const параметрыtemplate<typename F, size_t S> stack_function(const stack_function<F, S>&) = delete;
template<typename F, size_t S> stack_function(stack_function<F, S>&) = delete;
template<typename F, size_t S> stack_function(stack_function<F, S>&&) = delete;
template<typename F, size_t S> stack_function& operator=(const stack_function<F, S>&) = delete;
template<typename F, size_t S> stack_function& operator=(stack_function<F, S>&) = delete;
template<typename F, size_t S> stack_function& operator=(const stack_function<F, S>&&) = delete;
template<typename F, size_t S> void assign(const stack_function<F, S>&) = delete;
template<typename F, size_t S> void assign(stack_function<F, S>&) = delete;
template<typename F, size_t S> void assign(stack_function<F, S>&&) = delete;
stack_function() {}
stack_function(const stack_function& rhs) { base::copy_construct_(rhs); }
//note 9: ценность конструктора под вопросом (писал об этом выше)
stack_function(stack_function& rhs) { base::copy_construct_(rhs); }
//note 10: нужен std::move(rhs) вместо std::forward<base>
stack_function(stack_function&& rhs) { base::move_construct_(std::forward<base>(rhs)); }
stack_function& operator =(const stack_function& rhs) { base::destroy_(); base::copy_construct_(rhs); return *this; }
stack_function& operator =(stack_function& rhs) { base::destroy_(); base::copy_construct_(rhs); return *this; }
//note 11: нужен std::move(rhs) вместо std::forward<base>
stack_function& operator=(stack_function&& rhs) { base::destroy_(); base::move_construct_(std::forward<base>(rhs)); return *this; }
template<typename F> stack_function(F&& f) { base::construct_(std::forward<F>(f)); }
template<typename F> void assign(F&& f) { base::destroy_(); base::construct_(std::forward<F>(f)); }
template<typename F> stack_function& operator=(F&& f) { base::destroy_(); base::construct_(std::forward<F>(f)); return *this; }
//note 12: safe bool idiom более безопасно было бы применитьoperator bool() const { return !base::empty_(); }
};
#ifdef _WIN32
#pragma warning( pop )
#endif
Здравствуйте, uzhas, Вы писали:
U>//note 7: не следует ли добавить typename после public ? U>template<typename Function, size_t MaxSize> U>class stack_function : public details::get_stack_function_base<Function, MaxSize>::type
Не надо. В стандарте это отдельно оговорено, что при наследовании из контекста понятно, что ничего кроме типа не может быть. А значит typename не нужен.
Здравствуйте, uzhas, Вы писали: U>обложил нотами, может быть полезными, а могут быть и бесполезными U>на абсолютную правоту не претендую U>под катом, ибо много букв
Спасибо. U>//note 8: предположу, что достаточно оставить лишь const параметры
Неконстантные конструкторы копирования нужны чтобы отвадить компилятор от шаблонного конструктора.
Если на входе неконстантная ссылка, ADL считает конструктор <F>(F&&) предпочтительным, т.к. он best viable function, не требует конверсии noon-const->const. U>//note 6: я бы сделал rhs.destroy_() чтобы сбросить состояние у объекта, у которого мы все забрали. чаще всего так делают на практике имхо
Ну практики у меня нет, считал что по барабану, стандарт допускает, оно ж в валидном состоянии остается. Лишние действия, которые и так в деструкторе выполнятся.
Переписал без виртуальных вызовов. В принципе move/copy можно выкинуть если не нужны.
Хм, поздно я увидел эту тему Примерно с год назад делал похожую реализацию, изначально с ипользованием указателей на функции. В качестве идеи послужил такой код из реализации Disruptor
Но в тогда в проекте было ограничение на Visual C++ 2010. Можно, конечно, было пустить в ход Boost.Preprocessor и нагенерировать кучу специализаций шаблонов для разного количества аргументов, но в итогое хватило поддержки только сигнатуры void()
Полная реализация пот катом. Из бонусов — доступна информация о сигнатуре функции, более близкий к std::function интерфейс. Проверял только с Visual C++ 2013 Update 3 и GCC 4.9.0 на http://coliru.stacked-crooked.com/
В процессе разработки для меня осталось не понятным, почему при присваивании используется в качестве оператора присваивания используется перегрузка для неконстантной ссылки:
int f(int x) { return x; }
fixed_size_function<int(int), 256> fun(x), fun2;
fun2 = fun; // здесь Visual C++ 2013 использует fixed_size_function::operator=(fixed_size_function& src)
у вас, как и у ТС, одна и та же стилистическая ошибка: использование std::forward там, где явно нужен std::move (note 1 в ревью). на поведение программы не влияет, но для читателя может быть полезным
рассмотрим код:
//здесь нет универсальной ссылки, поэтому подправляю код
fixed_size_function(fixed_size_function&& src)
{
//move(std::forward<fixed_size_function>(src));
move(std::move(src)); // <-- более точно передает намерение: передвинуть (move) объект. тип нам уже известен
}
а вот здесь std::forward используется правильно, т.к. работаем с универсальной ссылкой
Здравствуйте, PM, Вы писали: PM>Полная реализация пот катом. Из бонусов — доступна информация о сигнатуре функции, более близкий к std::function интерфейс. Проверял только с Visual C++ 2013 Update 3 и GCC 4.9.0 на http://coliru.stacked-crooked.com/
Супер, объединил сейчас реализации. И название fixed_size_function конечно более удачное.
Добавил ещё один параметр в шаблон, Movable. Просто если overhead от move/copy не нужен хотелось бы ещё и быть уверенным что они не выполняются никогда.
void move(fixed_size_function&& src)
{
if (src.move_)
{
src.move_(&src.storage_, &storage_);
call_ = src.call_; src.call_ = nullptr;
copy_ = src.copy_; src.copy_ = nullptr;
move_ = src.move_; src.move_ = nullptr;
// здесь переместили деструктор, он не будет вызван для src
destroy_ = src.destroy_; src.destroy_ = nullptr;
}
}
Сделал так примерно:
void copy_vtable_(const fixed_size_function& rhs) {
call_ = rhs.call_;
dtor_ = rhs.dtor_;
copy_ = rhs.copy_;
move_ = rhs.move_;
}
...
void move_construct_(fixed_size_function&& rhs) {
if(!rhs.move_) return;
rhs.move_(&rhs.storage_, &storage_);
copy_vtable_(rhs);
rhs.reset();
}
PM>В процессе разработки для меня осталось не понятным, почему при присваивании используется в качестве оператора присваивания используется перегрузка для неконстантной ссылки:
Ну так опять таки просто потому что версия T& vs const T& версии оператора присваивания есть best viable function, т.к. приведение не нужно.
Здравствуйте, uzhas, Вы писали:
U>у вас, как и у ТС, одна и та же стилистическая ошибка: использование std::forward там, где явно нужен std::move (note 1 в ревью). на поведение программы не влияет, но для читателя может быть полезным
Ну как бы может и да, а может и нет... Всё зависит от того как воспринимать forward, мне кажется для forward подходит определение "передать параметр 'as-is'". Что в общем то применимо как к rvalue, так и к универсальным ссылкам.
move же это принудительный вызов rvalue версии. Т.е. forward он как бы forward, конструктор просто передает параметр другой ф-ции, не претендуя на его последующее использование, а вот явный move заставляет обращать внимание — а не воспользуются ли moved объектом после перемещения и т.п.
Здравствуйте, sokel, Вы писали:
S>Здравствуйте, uzhas, Вы писали:
U>>у вас, как и у ТС, одна и та же стилистическая ошибка: использование std::forward там, где явно нужен std::move (note 1 в ревью). на поведение программы не влияет, но для читателя может быть полезным
S>Ну как бы может и да, а может и нет... Всё зависит от того как воспринимать forward, мне кажется для forward подходит определение "передать параметр 'as-is'". Что в общем то применимо как к rvalue, так и к универсальным ссылкам. S>move же это принудительный вызов rvalue версии. Т.е. forward он как бы forward, конструктор просто передает параметр другой ф-ции, не претендуя на его последующее использование, а вот явный move заставляет обращать внимание — а не воспользуются ли moved объектом после перемещения и т.п.
Опять таки move это же не move, а просто каст к rvalue ссылке. Страуструп вон ругался, плохое говорит название.
Да вот например, пишем move конструктор для развесистого типа (ну вот не устраивает нас почему то default):
non-copyable специализация, кстати, полезна ещё и не только перформанса ради. В версии с копированием независимо от возможной надобности генерируется ф-ция копирования, что не позволяет засовывать в ф-цию movable-only функторы.
Т.е. f(std::bind(foo, unique_ptr<X>(new X()))) не прокатит. А вот в non-copyable версии запросто.
Запилил и copy и move-ability отдельными параметрами шаблона:
Здравствуйте, sokel, Вы писали: S>Опять таки move это же не move, а просто каст к rvalue ссылке. Страуструп вон ругался, плохое говорит название.
согласен, название плохое и я пытаюсь подстраивать код (с точки зрения удобочитаемости) под это именование
варианты
S>Да вот например, пишем move конструктор для развесистого типа (ну вот не устраивает нас почему то default): S>