将不可复制的闭包对象传递给 std::function 参数
在 C++14 中,lambda 表达式可以通过使用捕获初始值设定项从变量中移动来捕获变量.然而,这使得生成的闭包对象不可复制.如果我有一个接受 std::function
参数的现有函数(我不能改变),我不能传递闭包对象,因为 std::function
的构造函数要求给定的函子是 CopyConstructible
.
#include #include <内存>void doit(std::function f) {F();}int main(){std::unique_ptrp(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:错误:调用 '' 的隐式删除复制构造函数new _Functor(*__source._M_access<_Functor*>());^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:1946:8: 注意:在成员函数'std::_Function_base::_Base_manager<<lambda at test.cpp:10:7>的实例化>::_M_clone' 在此处请求_M_clone(__dest, __source, _Local_storage());^/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:2457:33: 注意:在成员函数'std::_Function_base::_Base_manager<<lambda at test.cpp:10:7>的实例化>::_M_manager' 在此处请求_M_manager = &_My_handler::_M_manager;^test.cpp:10:7: 注意:在函数模板特化的实例化中 'std::function::function<, void>'在这里请求doit([p = std::move(p)] () { std::cout << *p << std::endl; });^test.cpp:10:8: 注意:'' 的复制构造函数被隐式删除,因为字段 '' 已删除复制构造函数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:注意:'unique_ptr' 已在此处明确标记为已删除unique_ptr(const unique_ptr&) = 删除;^
是否有合理的解决方法?
使用 Ubuntu clang 版本 3.5-1~exp1 (trunk) 进行测试
解决方案有这个办法:
模板<类型名称签名>struct make_copyable_function_helper;模板<类型名称 R,类型名称... Args >struct make_copyable_function_helper<R(Args...)>{模板<类型名输入>std::function<R(Args...)>运算符()(输入&& i)const {自动 ptr = std::make_shared::type >( std::forward(i) );返回 [ptr]( Args... args )->R {返回 (*ptr)(std::forward(args)...);};}};模板make_copyable_function(输入&& i){return make_copyable_function_helper()( std::forward(i) );}
在这里我们创建一个指向我们数据的共享指针,然后创建一个捕获该共享指针的可复制 lambda,然后我们将该可复制 lambda 包装到请求签名的 std::function
中.>
在上述情况下,您只需:
doit( make_copyable_function( [p = std::move(p)] () { std::cout << *p << std::endl; }) );
稍微高级的版本推迟了类型擦除并添加了一层完美转发以减少开销:
templatestruct copyable_function {typedef typename std::decay::type stored_input;模板自动操作符()(Args&&...args)->decltype( std::declval()(std::forward(args)...)){返回 (*ptr)(std::forward(args));}copyable_function( input&& i ):ptr( std::make_shared( std::forward(i) ) ) {}copyable_function( copyable_function const& ) = default;私人的:std::shared_ptr指针;};模板<类型名输入>copyable_function<输入>make_copyable_function(输入&& i){返回 {std::forward(i)};}
它不需要您传入签名,并且在某些情况下效率会更高一些,但使用的技术更加晦涩.
在 C++14 中可以更简洁:
模板( std::forward(f) );返回 [spf](auto&&... args)->decltype(auto) {返回 (*spf)( decltype(args)(args)...);};}
完全不需要辅助类型.
In C++14, a lambda expression can capture variables by moving from them using capture initializers. However, this makes the resulting closure object non-copyable. If I have an existing function that takes a std::function
argument (that I cannot change), I cannot pass the closure object, because std::function
's constructor requires the given functor to be 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; });
}
This gives the following errors:
/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;
^
Is there a reasonable workaround?
Testing with Ubuntu clang version 3.5-1~exp1 (trunk)
解决方案There is this approach:
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) );
}
where we make a shared pointer to our data, then make a copyable lambda that captures that shared pointer, then we wrap that copyable lambda into a std::function
of the requested signature.
In your case above, you'd just:
doit( make_copyable_function<void()>( [p = std::move(p)] () { std::cout << *p << std::endl; } ) );
A slightly more advanced version defers the type erasure and adds a layer of perfect forwarding to reduce overhead:
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)};
}
which does not require you to pass the signature in, and can be slightly more efficient in a few cases, but uses more obscure techniques.
In C++14 with this can be made even more brief:
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)... );
};
}
doing away with the need for the helper type entirely.
相关文章