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

标签 c++ lambda move-semantics c++14

<分区>

在 C++14 中,lambda 表达式可以通过使用捕获初始值设定项从变量中 move 来捕获变量。但是,这使得生成的闭包对象不可复制。如果我有一个接受 std::function 参数(我无法更改)的现有函数,我无法传递闭包对象,因为 std::function 的构造函数要求给定的仿函数是 CopyConstructible

#include <iostream>
#include <memory>

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

int main()
{
    std::unique_ptr<int> 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 '<lambda at test.cpp:10:7>'
            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<<lambda at test.cpp:10:7>
      >::_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<<lambda at test.cpp:10:7>
      >::_M_manager' requested here
            _M_manager = &_My_handler::_M_manager;
                                       ^
test.cpp:10:7: note: in instantiation of function template specialization 'std::function<void
      ()>::function<<lambda at test.cpp:10:7>, 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) 进行测试

最佳答案

有这种方法:

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)... );
  };
}

完全不需要 helper 类型。

关于c++ - 将不可复制的闭包对象传递给 std::function 参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34470092/

相关文章:

java - 方法签名包括抛出异常?

c++ - 为什么不将右值引用定义为右值表达式?

c++ - 为什么要将类型 T 放置在需要 move 可构造的 vector 的末尾?

c++ - double 存储什么?

c++ - 可修改数组

python - 基于列名在 pandas 数据框的 lambda 表达式上使用 if else 语句

c++ - 在 lambda 中完美捕获完美的转发器(通用引用)

c++ - move 语义 : how best to understand/use them

C++:查找数组中的第二个最大元素

c++ - 不能使用 cv::fileStorgae 来保存 XML 文件