如何在 C++11 lambda 中通过移动(也称为右值引用)捕获?
我正在尝试写这样的东西:
std::unique_ptr<int> myPointer(new int); std::function<void(void)> example = [std::move(myPointer)]{ *myPointer = 4; };
在 C14 中,我们将拥有所谓的广义 lambda 捕获。这将启用移动捕获。以下将是 C14 中的合法代码:
using namespace std; // a unique_ptr is move-only auto u = make_unique<some_type>( some, parameters ); // move the unique_ptr into the lambda go.run( [ u = move(u) ] { do_something_with( u ); } );
另请注意,如果您需要将对象从 lambda 移动到您需要制作 lambda 的其他函数mutable。
mutable
go.run( [ u = move(u) ] mutable { do_something_with( std::move(u) ); } );
广义 lambda 捕获在某种意义上更为通用,捕获的变量可以用如下任何方式初始化:
auto lambda = [value = 0] mutable { return ++value; };
在 C++11 中,这还不可能,但有一些涉及辅助类型的技巧。幸运的是,Clang 3.4 编译器已经实现了这个很棒的功能。如果保持最近的发布速度,编译器将在 2013 年 12 月或 2014 年 1 月发布。
更新: Clang 3.4 编译器于 2014 年 1 月 6 日发布,具有上述功能。
make_rref这是帮助人工移动捕获的辅助函数的实现
make_rref
#include <cassert> #include <memory> #include <utility> template <typename T> struct rref_impl { rref_impl() = delete; rref_impl( T && x ) : x{std::move(x)} {} rref_impl( rref_impl & other ) : x{std::move(other.x)}, isCopied{true} { assert( other.isCopied == false ); } rref_impl( rref_impl && other ) : x{std::move(other.x)}, isCopied{std::move(other.isCopied)} { } rref_impl & operator=( rref_impl other ) = delete; T && move() { return std::move(x); } private: T x; bool isCopied = false; }; template<typename T> rref_impl<T> make_rref( T && x ) { return rref_impl<T>{ std::move(x) }; }
这是一个在我的 gcc 4.7.3 上成功运行的函数的测试用例。
int main() { std::unique_ptr<int> p{new int(0)}; auto rref = make_rref( std::move(p) ); auto lambda = [rref]() mutable -> std::unique_ptr<int> { return rref.move(); }; assert( lambda() ); assert( !lambda() ); }
这里的缺点是它lambda是可复制的,并且当复制失败的复制构造函数中的断言时会rref_impl导致运行时错误。以下可能是更好甚至更通用的解决方案,因为编译器会捕获错误。
lambda
rref_impl
这里还有一个想法,关于如何实现广义 lambda 捕获。该函数的使用capture()(其实现在下面找到)如下:
capture()
#include <cassert> #include <memory> int main() { std::unique_ptr<int> p{new int(0)}; auto lambda = capture( std::move(p), []( std::unique_ptr<int> & p ) { return std::move(p); } ); assert( lambda() ); assert( !lambda() ); }
这lambda是一个函子对象(几乎是一个真正的 lambda),std::move(p)它在传递给capture(). 的第二个参数capture是一个 lambda,它将捕获的变量作为参数。当lambda用作函数对象时,传递给它的所有参数将作为捕获变量之后的参数转发给内部 lambda。(在我们的例子中,没有进一步的参数需要转发)。本质上,与之前的解决方案相同。下面是如何capture实现的:
std::move(p)
capture
#include <utility> template <typename T, typename F> class capture_impl { T x; F f; public: capture_impl( T && x, F && f ) : x{std::forward<T>(x)}, f{std::forward<F>(f)} {} template <typename ...Ts> auto operator()( Ts&&...args ) -> decltype(f( x, std::forward<Ts>(args)... )) { return f( x, std::forward<Ts>(args)... ); } template <typename ...Ts> auto operator()( Ts&&...args ) const -> decltype(f( x, std::forward<Ts>(args)... )) { return f( x, std::forward<Ts>(args)... ); } }; template <typename T, typename F> capture_impl<T,F> capture( T && x, F && f ) { return capture_impl<T,F>( std::forward<T>(x), std::forward<F>(f) ); }
第二种解决方案也更简洁,因为如果捕获的类型不可复制,它会禁用复制 lambda。在第一个解决方案中,只能在运行时使用assert().
assert()