На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: Qraizer, Hsilgos
Страницы: (3) [1] 2 3  все  ( Перейти к последнему сообщению )  
> Аналог std::function , Реализация класса типа std::function, с movable-параметрами
    Известно, что для std::function обязательно требуется конструктор копирования.
    Однако, довольно регулярно требуется во всякие callback'и и потоки передавать non-copyable-объекты, типа std::unique_ptr, std::unique_lock и т.д.
    В свою очередь, std::bind возвращает вполне себе нормальный объект (не знаю какой), которому спокойно можно делать move.
    Поэтому решил написать обёртку для этого объекта:

    ExpandedWrap disabled
      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;
          };
      };


    Использование:

    ExpandedWrap disabled
      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");


    Но, что-то такая реализация мне не очень нравится, в основном из-за лишнего вызова виртуального метода.
    Подозреваю, что есть "более лучшее" решение, но не могу придумать ничего умного.
    Сообщение отредактировано: Олег М -
      Цитата Олег М @
      Известно, что для std::function обязательно требуется конструктор копирования.

      Можно подробнее? На сколько я знаю std::function можно присваивать даже лямбды, которые даже в теории не имеют конструкторы копирования.
      Если что - мне просто интересно. Я работал с std::function, но мало наверное. И не припомню ограничения с конструктором копирования.
      Сообщение отредактировано: KILLER -
        Цитата KILLER @
        Можно подробнее? На сколько я знаю std::function можно присваивать даже лямбды, которые даже в теории не имеют конструкторы копирования.

        Вот примеры, лямбдами и без.
        Выдают ошибку error: use of deleted function ...., на конструктор копирования.
        Если в них заменить unique_ptr на shared_ptr, то всё будет нормально
        ExpandedWrap disabled
          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");
          Там проблема именно при инициализации объекта std::function тем объектом, который возвращает std::bind
          ExpandedWrap disabled
            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 черт ногу сломит :-)

          Цитата
          лямбды, которые даже в теории не имеют конструкторы копирования

          В смысле? Лямбду же можно скопировать:
          ExpandedWrap disabled
            auto lambda = [&sp] (const std::string &s) { TestFunc(sp, s); };
            auto lambda2 = lambda;
            Цитата Serpentus @
            Почему так происходит, надо разбираться. Только там в исходном коде std черт ногу сломит :-)

            Не обязательно копаться в исходном коде. Там вроде заявлено, что std::function должна быть CopyConstructable. Хотелось бы знать - для чего?
              Цитата Олег М @
              Хотелось бы знать - для чего?

              Так она реализована :-) Вот конструктор, который используется в данном случае
              ExpandedWrap disabled
                    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, в другом случае эта функция может возвращать копируемый объект.
              ExpandedWrap disabled
                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
                }
                Цитата Serpentus @
                Комментарий четко дает понять, что с параметра, переданного при инициализации, чем бы он ни был, снимается и сохраняется копия

                Что–то не вижу я там копирования, вижу move.
                Ну и по–любому не понимаю – для чего там может понадобится обязательное копирование?
                  Цитата Олег М @
                  Там вроде заявлено, что std::function должна быть CopyConstructable. Хотелось бы знать - для чего?
                  Очевидно, что функциональные объекты интуитивно должны отвечать привычной программисту семантике значений. Так же как std::shared_ptr<> реализует это для указателей. Биндить в них значения суть нормальное явление, иначе зачем вообще использовать объекты вместо просто функций, однако это и подразумевает CopyConstructable для биндов.
                  Пытаться сделать из не CopyConstructable объекта, того же std::uniqur_ptr<>, например, CopyConstructable объект не есть хорошая идея. Я бы не хотел какого-нибудь damned hell в своём проекте. Это когда в ряде случаев объекты вдруг оказываются пусты, потому что перемещены вовнутрь неведомых дебрей, и теперь живут там... или уже нет, хрен разберёшь. Иметь две разных семантики для одной парадигмы функционального объекта будет провоцировать на мат в дальнейшем. Лучше просто отказаться от биндов не CopyConstructable значений.
                    Цитата Qraizer @
                    Лучше просто отказаться от биндов не CopyConstructable значений.

                    В смысле, отказаться от передачи unique_ptr в поток или в callback? Или рассчитывать, что будут делаться копии твоих списков, строк и т.д.?
                      В случае с потоками, вроде разобрался. Там прикол в том, что в отличие от std::bind, функция вызывается только один раз. И желательно, чтоб сразу после вызова все параметры удалялись, да и сама функция тоже. Чего, кстати, std::thread вроде не делает.
                      Написал, специально для потоков, вот такую штуку, не уверен, правда, что правильно

                      ExpandedWrap disabled
                        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)...);
                        }
                        Цитата Олег М @
                        Что–то не вижу я там копирования, вижу move.

                        move - это для передачи параметра в _Reset, копирование, очевидно, происходит в ней.
                          Цитата Serpentus @
                          move - это для передачи параметра в _Reset, копирование, очевидно, происходит в ней.

                          Всё равно непонятно, для чего там нужно копирование.
                            Олег М
                            Копирование нужно, чтобы сохранить нечто, которым мы инициализируем std::function, где-то там у себя в члене класса. Надо запомнить то, что мы потом вызывать будем. Не ссылку же на него сохранять.
                            Я говорю, по исходникам лазить - умрешь, но можно предположить, что там что-то вроде такого (очень упрощенно):
                            ExpandedWrap disabled
                              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;
                              };
                              Цитата Serpentus @
                              но можно предположить, что там что-то вроде такого (очень упрощенно):

                              У меня разве не то же самое, только без копии?
                                Олег М
                                Так вопрос же вроде был, зачем в std::funtion требование CopyConstructable, не? Или ты имеешь в виду, почему они не сделали что-то типа того, как у тебя? Это можно только гадать. Может просто не додумались. :) Или не посчитали нужным (или даже вредным, Qraizer объяснил почему). А еще у вызова через std::function нулевой оверхед, а у вызова через std::unique_ptr<CFunction> - нет (пусть и не большой).
                                0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                0 пользователей:
                                Страницы: (3) [1] 2 3  все


                                Рейтинг@Mail.ru
                                [ Script execution time: 0,0535 ]   [ 17 queries used ]   [ Generated: 28.03.24, 23:23 GMT ]