首页 文章

std :: function的仅移动版本

提问于
浏览
47

因为 std::function 是可复制的,所以标准要求用于构造它的callables也是可复制的:

n337(20.8.11.2.1)

template <class F> function(F f);要求:F应为CopyConstructible . 对于参数类型ArgTypes和返回类型R,f应为Callable(20.8.11.2).A的复制构造函数和析构函数不应抛出异常 .

这意味着无法从不可复制的绑定对象或捕获仅移动类型(如 std::unique_ptr )的lambda形成 std::function .

似乎可以为仅移动的callables实现这样一个仅移动的包装器 . 是否有一个标准的库只移动等效于 std::function ,或者,这个问题有一个共同的解决方法吗?

2 回答

  • 16

    不,C std 库中没有 std::function 的仅移动版本 . (截至C 14)

    Fastest possible delegates是类似于 std::function 类的实现,碰巧比许多 std 库中的大多数 std::function 实现更快,并且应该很容易分成 movecopy 版本 .

    move 唯一函数对象包含到具有转发 operator() 的类中的 shared_ptr<F> 是另一种方法 .

    这是一个 task 草图:

    template<class Sig>
    struct task;
    
    namespace details {
      template<class Sig>
      struct task_iimpl;
      template<class R, class...Args>
      struct task_iimpl<R(Args...)> {
        virtual ~task_iimpl() {}
        virtual R invoke(Args&&...args) const = 0;
      };
      template<class F, class Sig>
      struct task_impl;
      template<class F, class R, class...Args>
      struct task_impl<F,R(Args...)>:
        task_iimpl<R(Args...)>
      {
        F f;
        template<class T>
        task_impl(T&& t):f(std::forward<T>(t)) {}
        virtual R invoke(Args&&...args) const override {
          return f( std::forward<Args>(args...) );
        }
      };
      template<class F, class...Args>
      struct task_impl<F,void(Args...)>:
        task_iimpl<void(Args...)>
      {
        F f;
        template<class T>
        task_impl(T&& t):f(std::forward<T>(t)) {}
        virtual void invoke(Args&&...args) const override {
          f( std::forward<Args>(args...) );
        }
      };
    }
    template<class R, class...Args>
    struct task<R(Args...)> {
      virtual ~task_iimpl() {}
      R operator()(Args...args) const {
        return pImpl->invoke(std::forward<Args>(args...));
      }
      explicit operator bool()const{ return static_cast<bool>(pImpl); }
      task(task &&)=default;
      task& operator=(task &&)=default;
      task()=default;
    
      // and now for a mess of constructors
      // the rule is that a task can be constructed from anything
      // callable<R(Args...)>, destroyable, and can be constructed
      // from whatever is passed in.  The callable feature is tested for
      // in addition, if constructed from something convertible to `bool`,
      // then if that test fails we construct an empty task.  This makes us work
      // well with empty std::functions and function pointers and other tasks
      // that are call-compatible, but not exactly the same:
      struct from_func_t {};
      template<class F,
        class dF=std::decay_t<F>,
        class=std::enable_if_t<!std::is_same<dF, task>{}>,
        class FR=decltype(std::declval<F const&>()(std::declval<Args>()...)),
        std::enable_if_t<std::is_same<R, void>{} || std::is_convertible<FR, R>{} >*=0,
        std::enable_if_t<std::is_convertible<dF, bool>{}>*=0
      >
      task(F&& f):
        task(
          static_cast<bool>(f)?
          task( from_func_t{}, std::forward<F>(f) ):
          task()
        )
      {}
      template<class F,
        class dF=std::decay_t<F>,
        class=std::enable_if_t<!std::is_same<dF, task>{}>,
        class FR=decltype(std::declval<F const&>()(std::declval<Args>()...)),
        std::enable_if_t<std::is_same<R, void>{} || std::is_convertible<FR, R>{} >*=0,
        std::enable_if_t<!std::is_convertible<dF, bool>{}>*=0
      >
      task(F&& f):
        task( from_func_t{}, std::forward<F>(f) )
      {}
    
      task(std::nullptr_t):task() {}
      // overload resolution helper when signatures match exactly:
      task( R(*pf)(Args...) ):
        task( pf?task( from_func_t{}, pf ):task() )
      {}
    private:
      template<class F,
        class dF=std::decay_t<F>
      >
      task(from_func_t, F&& f):
        pImpl( std::make_unique<details::task_impl<dF,R(Args...)>>(
          std::forward<F>(f)
        )
      {}
    
      std::unique_ptr<details::task_iimpl<R(Args...)> pImpl;
    };
    

    但它还没有经过测试或编译,我只是写了它 .

    更具工业强度的版本将包括一个小缓冲区优化(SBO)来存储小的可调用(假设它们是可移动的;如果不是可移动的,则存储在堆上以允许移动),以及一个get-pointer-if-you-the-the- type-right(如 std::function ) .

  • 2

    正如其他人所指出的那样,库中没有 std::function 的只移动版本 . 以下是一种解决方法:重用(滥用?) std::function 并允许它接受仅移动类型 . 它在很大程度上受到评论中的dyp's implementation的启发,所以很多功劳归功于他:

    #include <functional>
    #include <iostream>
    #include <type_traits>
    #include <utility>
    
    template<typename T>
    class unique_function : public std::function<T>
    {
        template<typename Fn, typename En = void>
        struct wrapper;
    
        // specialization for CopyConstructible Fn
        template<typename Fn>
        struct wrapper<Fn, std::enable_if_t< std::is_copy_constructible<Fn>::value >>
        {
            Fn fn;
    
            template<typename... Args>
            auto operator()(Args&&... args) { return fn(std::forward<Args>(args)...); }
        };
    
        // specialization for MoveConstructible-only Fn
        template<typename Fn>
        struct wrapper<Fn, std::enable_if_t< !std::is_copy_constructible<Fn>::value
            && std::is_move_constructible<Fn>::value >>
        {
            Fn fn;
    
            wrapper(Fn&& fn) : fn(std::forward<Fn>(fn)) { }
    
            wrapper(wrapper&&) = default;
            wrapper& operator=(wrapper&&) = default;
    
            // these two functions are instantiated by std::function
            // and are never called
            wrapper(const wrapper& rhs) : fn(const_cast<Fn&&>(rhs.fn)) { throw 0; } // hack to initialize fn for non-DefaultContructible types
            wrapper& operator=(wrapper&) { throw 0; }
    
            template<typename... Args>
            auto operator()(Args&&... args) { return fn(std::forward<Args>(args)...); }
        };
    
        using base = std::function<T>;
    
    public:
        unique_function() noexcept = default;
        unique_function(std::nullptr_t) noexcept : base(nullptr) { }
    
        template<typename Fn>
        unique_function(Fn&& f) : base(wrapper<Fn>{ std::forward<Fn>(f) }) { }
    
        unique_function(unique_function&&) = default;
        unique_function& operator=(unique_function&&) = default;
    
        unique_function& operator=(std::nullptr_t) { base::operator=(nullptr); return *this; }
    
        template<typename Fn>
        unique_function& operator=(Fn&& f)
        { base::operator=(wrapper<Fn>{ std::forward<Fn>(f) }); return *this; }
    
        using base::operator();
    };
    
    using std::cout; using std::endl;
    
    struct move_only
    {
        move_only(std::size_t) { }
    
        move_only(move_only&&) = default;
        move_only& operator=(move_only&&) = default;
    
        move_only(move_only const&) = delete;
        move_only& operator=(move_only const&) = delete;
    
        void operator()() { cout << "move_only" << endl; }
    };
    
    int main()
    {
        using fn = unique_function<void()>;
    
        fn f0;
        fn f1 { nullptr };
        fn f2 { [](){ cout << "f2" << endl; } }; f2();
        fn f3 { move_only(42) }; f3();
        fn f4 { std::move(f2) }; f4();
    
        f0 = std::move(f3); f0();
        f0 = nullptr;
        f2 = [](){ cout << "new f2" << endl; }; f2();
        f3 = move_only(69); f3();
    
        return 0;
    }
    

    Working version to coliru .

相关问题