Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[52.90.50.252] |
|
Страницы: (3) [1] 2 3 все ( Перейти к последнему сообщению ) |
Сообщ.
#1
,
|
|
|
Известно, что для std::function обязательно требуется конструктор копирования.
Однако, довольно регулярно требуется во всякие callback'и и потоки передавать non-copyable-объекты, типа std::unique_ptr, std::unique_lock и т.д. В свою очередь, std::bind возвращает вполне себе нормальный объект (не знаю какой), которому спокойно можно делать move. Поэтому решил написать обёртку для этого объекта: template <typename> struct CBinder; template <size_t... Indexes> struct CBinder<std::index_sequence<Indexes...>> { template <typename TFunc, typename... TT> static auto Bind(TFunc &&func, TT&&... args) { typedef std::tuple < decltype(std::placeholders::_1), decltype(std::placeholders::_2), decltype(std::placeholders::_3), decltype(std::placeholders::_4), decltype(std::placeholders::_5), decltype(std::placeholders::_6), decltype(std::placeholders::_7), decltype(std::placeholders::_8), decltype(std::placeholders::_9), decltype(std::placeholders::_10) > TPlaceholders; return std::bind(std::forward<TFunc>(func), std::forward<TT>(args)..., typename std::tuple_element<Indexes, TPlaceholders>::type{}...); } }; template <typename TRes, typename... TArgs> class CFunction { public: typedef std::unique_ptr<CFunction> TPtr; template <typename TFunc, typename... TT> static TPtr Make(TFunc &&func, TT&&... args) { typedef decltype(std::make_index_sequence<sizeof...(TArgs)>()) TArgsIndexes; auto fn = CBinder<TArgsIndexes>::Bind(std::forward<TFunc>(func), std::forward<TT>(args)...); return std::make_unique<CFunctionImpl<decltype(fn), CFunction>>(std::move(fn)); } virtual ~CFunction() {;} template <typename... TT> TRes Call(TT&&... args) { return _Call(std::forward<TT>(args)...); } template <typename... TT> TRes operator()(TT&&... args) { return Call(std::forward<TT>(args)...); } protected: virtual TRes _Call(TArgs... args) = 0; template <typename TFunc, typename T> class CFunctionImpl : public T { public: CFunctionImpl(TFunc &&fn) : m_fn(std::forward<TFunc>(fn)) { } virtual TRes _Call(TArgs... args) override { return m_fn(args...); } protected: TFunc m_fn; }; }; Использование: static void TestFunc(const char *__file__, int __line__, size_t p1, const char *p2) { std::cout << p1 << std::endl; std::cout << p2 << std::endl; std::cout << __file__ << std::endl; std::cout << __line__ << std::endl; } ........................ typedef CFunction<void, size_t, const char *> TFunc; auto spFunc = TFunc::Make(TestFunc, __FILE__, __LINE__); spFunc->Call(111, "22222"); std::unique_ptr<std::string> sp(new std::string("!!!!!!!!!!")); auto spFunc2 = TFunc::Make([sp = std::move(sp)](size_t p1, const char *p2) { std::cout << p1 << std::endl; std::cout << p2 << std::endl; std::cout << *sp << std::endl; }); spFunc2->Call(111, "22222"); Но, что-то такая реализация мне не очень нравится, в основном из-за лишнего вызова виртуального метода. Подозреваю, что есть "более лучшее" решение, но не могу придумать ничего умного. |
Сообщ.
#2
,
|
|
|
Цитата Олег М @ Известно, что для std::function обязательно требуется конструктор копирования. Можно подробнее? На сколько я знаю std::function можно присваивать даже лямбды, которые даже в теории не имеют конструкторы копирования. Если что - мне просто интересно. Я работал с std::function, но мало наверное. И не припомню ограничения с конструктором копирования. |
Сообщ.
#3
,
|
|
|
Цитата KILLER @ Можно подробнее? На сколько я знаю std::function можно присваивать даже лямбды, которые даже в теории не имеют конструкторы копирования. Вот примеры, лямбдами и без. Выдают ошибку error: use of deleted function ...., на конструктор копирования. Если в них заменить unique_ptr на shared_ptr, то всё будет нормально static void TestFunc(std::unique_ptr<std::string> &sp, const std::string &s) { } typedef std::function<void(const std::string &)> TFunc; std::unique_ptr<std::string> sp(new std::string()) ; TFunc fn = std::bind(TestFunc, std::move(sp), std::placeholders::_1); fn("1111111111111111111111"); TFunc fn2 = std::bind([](std::unique_ptr<std::string> &sp, const std::string &s) { }, std::move(sp), std::placeholders::_1); fn2("1111111111111111111111"); TFunc fn3 = std::bind([sp = std::move(sp)](const std::string &s) { }, std::placeholders::_1); fn3("1111111111111111111111"); |
Сообщ.
#4
,
|
|
|
Там проблема именно при инициализации объекта std::function тем объектом, который возвращает std::bind
static void TestFunc(std::unique_ptr<std::string> &sp, const std::string &s) { std::cout << *sp << " | " << s << std::endl; } typedef std::function<void(const std::string &)> TFunc; typedef std::function<void(std::unique_ptr<std::string>&, const std::string &)> TFunc2; static void Test1() { std::unique_ptr<std::string> sp(new std::string("0")); auto lambda = [&sp] (const std::string &s) { TestFunc(sp, s); }; // std::function инициализируется лямбда-функцией // откомпилируется нормально TFunc func_obj1 = lambda; func_obj1("1"); // std::function инициализируется указателем на функцию // откомпилируется нормально TFunc2 func_obj2 = TestFunc; func_obj2(sp, "2"); // объект, возвращаемый std::bind // откомпилируется нормально auto bind_obj = std::bind(TestFunc, std::move(sp), std::placeholders::_1); bind_obj("3"); // а вот это, действительно, не компилируется TFunc fn = bind_obj; } Почему так происходит, надо разбираться. Только там в исходном коде std черт ногу сломит :-) Цитата лямбды, которые даже в теории не имеют конструкторы копирования В смысле? Лямбду же можно скопировать: auto lambda = [&sp] (const std::string &s) { TestFunc(sp, s); }; auto lambda2 = lambda; |
Сообщ.
#5
,
|
|
|
Цитата Serpentus @ Почему так происходит, надо разбираться. Только там в исходном коде std черт ногу сломит :-) Не обязательно копаться в исходном коде. Там вроде заявлено, что std::function должна быть CopyConstructable. Хотелось бы знать - для чего? |
Сообщ.
#6
,
|
|
|
Цитата Олег М @ Хотелось бы знать - для чего? Так она реализована :-) Вот конструктор, который используется в данном случае template<class _Fx, class _Inv_res = typename _Mybase::template _Result_of_invoking_t<_Fx&>, class = typename _Mybase::template _Enable_if_returnable_t<_Inv_res> > function(_Fx _Func) { // construct wrapper holding copy of _Func this->_Reset(_STD move(_Func)); } Комментарий четко дает понять, что с параметра, переданного при инициализации, чем бы он ни был, снимается и сохраняется копия. Тот объект, который возвращает std::bind, копирование не позволяет, отсюда результат. Речь идет про тот частный случай std::bind, в другом случае эта функция может возвращать копируемый объект. auto bind_obj = std::bind(TestFunc, std::move(sp), std::placeholders::_1); auto bind_obj2 = bind_obj; // Не скомпилируется static void foo(int i) {} static void Test() { auto bind_obj = std::bind(foo, 1); auto bind_obj2 = bind_obj; // OK } |
Сообщ.
#7
,
|
|
|
Цитата Serpentus @ Комментарий четко дает понять, что с параметра, переданного при инициализации, чем бы он ни был, снимается и сохраняется копия Что–то не вижу я там копирования, вижу move. Ну и по–любому не понимаю – для чего там может понадобится обязательное копирование? |
Сообщ.
#8
,
|
|
|
Цитата Олег М @ Очевидно, что функциональные объекты интуитивно должны отвечать привычной программисту семантике значений. Так же как std::shared_ptr<> реализует это для указателей. Биндить в них значения суть нормальное явление, иначе зачем вообще использовать объекты вместо просто функций, однако это и подразумевает CopyConstructable для биндов. Там вроде заявлено, что std::function должна быть CopyConstructable. Хотелось бы знать - для чего? Пытаться сделать из не CopyConstructable объекта, того же std::uniqur_ptr<>, например, CopyConstructable объект не есть хорошая идея. Я бы не хотел какого-нибудь damned hell в своём проекте. Это когда в ряде случаев объекты вдруг оказываются пусты, потому что перемещены вовнутрь неведомых дебрей, и теперь живут там... или уже нет, хрен разберёшь. Иметь две разных семантики для одной парадигмы функционального объекта будет провоцировать на мат в дальнейшем. Лучше просто отказаться от биндов не CopyConstructable значений. |
Сообщ.
#9
,
|
|
|
Цитата Qraizer @ Лучше просто отказаться от биндов не CopyConstructable значений. В смысле, отказаться от передачи unique_ptr в поток или в callback? Или рассчитывать, что будут делаться копии твоих списков, строк и т.д.? |
Сообщ.
#10
,
|
|
|
В случае с потоками, вроде разобрался. Там прикол в том, что в отличие от std::bind, функция вызывается только один раз. И желательно, чтоб сразу после вызова все параметры удалялись, да и сама функция тоже. Чего, кстати, std::thread вроде не делает.
Написал, специально для потоков, вот такую штуку, не уверен, правда, что правильно template <typename> struct CThreadProcInvoker; template <size_t... Indexes> struct CThreadProcInvoker<std::index_sequence<Indexes...>> { template <typename TFunc, typename TArgs, typename... TT> static void Invoke(TFunc func, TArgs args, TT&&... args2) { std::invoke(func, std::move(std::get<Indexes>(args))..., std::forward<TT>(args2)...); } }; template <typename TFunc, typename... TArgs> class CThreadProc { public: explicit CThreadProc(TFunc &&fn, TArgs&&... args) : m_fn(std::forward<TFunc>(fn)) , m_args(std::forward<TArgs>(args)...) { } CThreadProc(CThreadProc &&) = default; CThreadProc& operator =(CThreadProc &&) = default; CThreadProc(const CThreadProc &) = delete; CThreadProc& operator =(const CThreadProc &) = delete; template <typename... TT> requires std::is_move_constructible<TFunc>::value void operator()(TT&&... args) { CThreadProcInvoker<std::index_sequence_for<TArgs...>>::Invoke(std::move(m_fn), std::move(m_args), std::forward<TT>(args)...); } protected: TFunc m_fn; std::tuple<std::decay_t<TArgs>...> m_args; }; template <typename TFunc, typename... TArgs> auto MakeThreadProc(TFunc &&fn, TArgs&&... args) { return CThreadProc<TFunc, TArgs...>(std::forward<TFunc>(fn), std::forward<TArgs>(args)...); } |
Сообщ.
#11
,
|
|
|
Цитата Олег М @ Что–то не вижу я там копирования, вижу move. move - это для передачи параметра в _Reset, копирование, очевидно, происходит в ней. |
Сообщ.
#12
,
|
|
|
Цитата Serpentus @ move - это для передачи параметра в _Reset, копирование, очевидно, происходит в ней. Всё равно непонятно, для чего там нужно копирование. |
Сообщ.
#13
,
|
|
|
Олег М
Копирование нужно, чтобы сохранить нечто, которым мы инициализируем std::function, где-то там у себя в члене класса. Надо запомнить то, что мы потом вызывать будем. Не ссылку же на него сохранять. Я говорю, по исходникам лазить - умрешь, но можно предположить, что там что-то вроде такого (очень упрощенно): struct _CallableBase { virtual ~_CallableBase() = default; }; template<typename T> struct _Callable : public _CallableBase, public T { }; template<...> class function { ... template<typename TFunc> function(TFunc func) { // сохраняем что-то, что потом можно вызывать через operator () call_obj = new _Callable<TFunc>(func); // тут с func надо снять копию } ... private: _CallableBase* call_obj; }; |
Сообщ.
#14
,
|
|
|
Цитата Serpentus @ но можно предположить, что там что-то вроде такого (очень упрощенно): У меня разве не то же самое, только без копии? |
Сообщ.
#15
,
|
|
|
Олег М
Так вопрос же вроде был, зачем в std::funtion требование CopyConstructable, не? Или ты имеешь в виду, почему они не сделали что-то типа того, как у тебя? Это можно только гадать. Может просто не додумались. Или не посчитали нужным (или даже вредным, Qraizer объяснил почему). А еще у вызова через std::function нулевой оверхед, а у вызова через std::unique_ptr<CFunction> - нет (пусть и не большой). |