将不可复制的闭包对象传递给std :: function参数

 一截藏青线 发布于 2023-02-07 14:41

在C++ 14中,lambda表达式可以通过使用捕获初始化器从它们移动来捕获变量.但是,这会使得结果闭包对象不可复制.如果我有一个带有std::function参数的现有函数(我无法改变),我无法传递闭包对象,因为std::function构造函数需要给定的函子CopyConstructible.

#include 
#include 

void doit(std::function f) {
    f();
}

int main()
{
    std::unique_ptr p(new int(5));
    doit([p = std::move(p)] () { std::cout << *p << std::endl; });
}

这会出现以下错误:

/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:1911:10: error: 
      call to implicitly-deleted copy constructor of ''
            new _Functor(*__source._M_access<_Functor*>());
                ^        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:1946:8: note: in
      instantiation of member function 'std::_Function_base::_Base_manager<
      >::_M_clone' requested here
              _M_clone(__dest, __source, _Local_storage());
              ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:2457:33: note: in
      instantiation of member function 'std::_Function_base::_Base_manager<
      >::_M_manager' requested here
            _M_manager = &_My_handler::_M_manager;
                                       ^
test.cpp:10:7: note: in instantiation of function template specialization 'std::function::function<, void>' requested here
        doit([p = std::move(p)] () { std::cout << *p << std::endl; });
             ^
test.cpp:10:8: note: copy constructor of '' is implicitly deleted because field '' has a deleted
      copy constructor
        doit([p = std::move(p)] () { std::cout << *p << std::endl; });
              ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/bits/unique_ptr.h:273:7: note: 
      'unique_ptr' has been explicitly marked deleted here
      unique_ptr(const unique_ptr&) = delete;
      ^

有合理的解决方法吗?

使用Ubuntu clang版本3.5-1~exp1(trunk)进行测试

2 个回答
  • 有这种方法:

    template< typename signature >
    struct make_copyable_function_helper;
    template< typename R, typename... Args >
    struct make_copyable_function_helper<R(Args...)> {
      template<typename input>
      std::function<R(Args...)> operator()( input&& i ) const {
        auto ptr = std::make_shared< typename std::decay<input>::type >( std::forward<input>(i) );
        return [ptr]( Args... args )->R {
          return (*ptr)(std::forward<Args>(args)...);
        };
      }
    };
    
    template< typename signature, typename input >
    std::function<signature> make_copyable_function( input && i ) {
      return make_copyable_function_helper<signature>()( std::forward<input>(i) );
    }
    

    我们创建一个指向我们数据的共享指针,然后创建一个可复制的lambda来捕获该共享指针,然后我们将该可复制的lambda包装到std::function请求的签名中.

    在您的情况下,您只需:

    doit( make_copyable_function<void()>( [p = std::move(p)] () { std::cout << *p << std::endl; } ) );
    

    更高级的版本推迟了类型擦除,并添加了一层完美的转发以减少开销:

    template<typename input>
    struct copyable_function {
      typedef typename std::decay<input>::type stored_input;
      template<typename... Args>
      auto operator()( Args&&... args )->
        decltype( std::declval<input&>()(std::forward<Args>(args)...) )
      {
        return (*ptr)(std::forward<Args>(args));
      }
      copyable_function( input&& i ):ptr( std::make_shared<stored_input>( std::forward<input>(i) ) ) {}
      copyable_function( copyable_function const& ) = default;
    private:
      std::shared_ptr<stored_input> ptr;
    };
    template<typename input>
    copyable_function<input> make_copyable_function( input&& i ) {
      return {std::forward<input>(i)}; 
    }
    

    这不需要你传递签名,在一些情况下可以略微提高效率,但使用更加模糊的技术.

    在C++ 14中,可以做得更简单:

    template< class F >
    auto make_copyable_function( F&& f ) {
      using dF=std::decay_t<F>;
      auto spf = std::make_shared<dF>( std::forward<F>(f) );
      return [spf](auto&&... args)->decltype(auto) {
        return (*spf)( decltype(args)(args)... );
      };
    }
    

    彻底消除了对助手类型的需求.

    2023-02-07 14:42 回答
  • 如果闭包对象的生命周期不是问题,您可以在引用包装器中传递它:

    int main()
    {
        std::unique_ptr<int> p(new int(5));
        auto f = [p = std::move(p)]{
            std::cout << *p << std::endl;
        };
        doit(std::cref(f));
    }
    

    这显然不适用于所有场景,但它适用于您的示例程序.

    编辑:瞥一眼N3797(C++ 14工作草案)§20.9.11.2.1[func.wrap.func.con] p7,CopyConstructible要求仍然存在.我想知道是否存在一个不能放松的技术原因MoveConstructible,或者委员会是否只是没有解决它?

    编辑:回答我自己的问题:std::function是的CopyConstructible,所以包裹的仿函数也需要CopyConstructible.

    2023-02-07 14:44 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有