在lambda中移动捕获

如何通过移动(也称为右值引用)在C ++ 11 lambda中捕获?

我想写这样的东西:

std::unique_ptr<int> myPointer(new int); std::function<void(void)> = [std::move(myPointer)]{ (*myPointer) = 4; }; 

C ++中的广义lambda捕获

在C ++ 14中,我们将有所谓的广义lambda捕获 。 这使移动捕捉。 以下是C ++ 14中的合法代码:

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

但是在这个意义上说,捕获variables可以用类似这样的东西来初始化:

 auto lambda = [value = 0] mutable { return ++value; }; 

在C ++ 11中这是不可能的,但有一些技巧涉及到帮助types。 幸运的是,Clang 3.4编译器已经实现了这个function。 编译器将在2013年12月或2014年1月发布 ,如果最近的发布速度将被保留。

更新: Clang 3.4编译器于2014年1月6日发布,具有上述function。

移动捕捉的解决方法

这里有一个辅助函数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) }; } 

这个函数的testing用例在我的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的拷贝构造函数中的rref_impl失败,导致运行时错误。 以下可能是一个更好,甚至更通用的解决scheme,因为编译器会捕获错误。

在C ++ 11中模拟通用的lambda捕获

这里有一个更多的想法,关于如何实现广义的lambda捕获。 使用函数capture() (其实现可以在下面find)如下所示:

 #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是一个functor对象(几乎是一个真正的lambda),它已经捕获std::move(p)因为它传递给capture()capture的第二个参数是一个lambda,它将捕获的variables作为参数。 当lambda用作函数对象时,传递给它的所有参数将被转发到内部lambda作为捕获variables之后的参数。 (在我们的情况下,没有进一步的论据被转发)。 基本上,和以前的解决scheme一样。 以下是如何实现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) ); } 

这第二个解决scheme也更清洁,因为如果捕获的types不可复制,它将禁用复制lambda。 在第一个解决scheme中,只能在运行时使用assert()进行检查。

你也可以使用std::bind来捕获unique_ptr

 std::function<void()> f = std::bind( [] (std::unique_ptr<int>& p) { *p=4; }, std::move(myPointer) ); 

你可以使用std::bind实现你想要的大部分,就像这样:

 std::unique_ptr<int> myPointer(new int{42}); auto lambda = std::bind([](std::unique_ptr<int>& myPointerArg){ *myPointerArg = 4; myPointerArg.reset(new int{237}); }, std::move(myPointer)); 

这里的技巧是不是捕获捕获列表中的移动对象,而是通过std::bind使其成为一个参数,然后使用部分应用程序使其消失。 请注意,lambda需要通过引用 ,因为它实际上存储在绑定对象中。 我还添加了写入实际可移动对象的代码,因为这是您可能想要做的事情。

在C ++ 14中,可以使用通用的lambda捕获来实现相同的目的,使用下面的代码:

 std::unique_ptr<int> myPointer(new int{42}); auto lambda = [myPointerCapture = std::move(myPointer)]() mutable { *myPointerCapture = 56; myPointerCapture.reset(new int{237}); }; 

但是这段代码并没有通过std::bind C ++ 11购买任何你没有的东西。 (在某些情况下,通用的lambda捕获function更强大,但在这种情况下不是。)

现在只有一个问题。 你想把这个函数放在一个std::function ,但是这个类要求函数是CopyConstructible ,但不是,它只是MoveConstructible,因为它存储了一个不是CopyConstructible的std::unique_ptr

你用包装类和另一个间接级别来解决这个问题,但也许你根本不需要std::function 。 根据您的需要,您可以使用std::packaged_task ; 它会做和std::function一样的工作,但它不需要函数是可复制的,只能移动(同样, std::packaged_task只能移动)。 缺点是,因为它打算与std :: future一起使用,所以只能调用一次。

这是一个简短的程序,显示所有这些概念。

 #include <functional> // for std::bind #include <memory> // for std::unique_ptr #include <utility> // for std::move #include <future> // for std::packaged_task #include <iostream> // printing #include <type_traits> // for std::result_of #include <cstddef> void showPtr(const char* name, const std::unique_ptr<size_t>& ptr) { std::cout << "- &" << name << " = " << &ptr << ", " << name << ".get() = " << ptr.get(); if (ptr) std::cout << ", *" << name << " = " << *ptr; std::cout << std::endl; } // If you must use std::function, but your function is MoveConstructable // but not CopyConstructable, you can wrap it in a shared pointer. template <typename F> class shared_function : public std::shared_ptr<F> { public: using std::shared_ptr<F>::shared_ptr; template <typename ...Args> auto operator()(Args&&...args) const -> typename std::result_of<F(Args...)>::type { return (*(this->get()))(std::forward<Args>(args)...); } }; template <typename F> shared_function<F> make_shared_fn(F&& f) { return shared_function<F>{ new typename std::remove_reference<F>::type{std::forward<F>(f)}}; } int main() { std::unique_ptr<size_t> myPointer(new size_t{42}); showPtr("myPointer", myPointer); std::cout << "Creating lambda\n"; #if __cplusplus == 201103L // C++ 11 // Use std::bind auto lambda = std::bind([](std::unique_ptr<size_t>& myPointerArg){ showPtr("myPointerArg", myPointerArg); *myPointerArg *= 56; // Reads our movable thing showPtr("myPointerArg", myPointerArg); myPointerArg.reset(new size_t{*myPointerArg * 237}); // Writes it showPtr("myPointerArg", myPointerArg); }, std::move(myPointer)); #elif __cplusplus > 201103L // C++14 // Use generalized capture auto lambda = [myPointerCapture = std::move(myPointer)]() mutable { showPtr("myPointerCapture", myPointerCapture); *myPointerCapture *= 56; showPtr("myPointerCapture", myPointerCapture); myPointerCapture.reset(new size_t{*myPointerCapture * 237}); showPtr("myPointerCapture", myPointerCapture); }; #else #error We need C++11 #endif showPtr("myPointer", myPointer); std::cout << "#1: lambda()\n"; lambda(); std::cout << "#2: lambda()\n"; lambda(); std::cout << "#3: lambda()\n"; lambda(); #if ONLY_NEED_TO_CALL_ONCE // In some situations, std::packaged_task is an alternative to // std::function, eg, if you only plan to call it once. Otherwise // you need to write your own wrapper to handle move-only function. std::cout << "Moving to std::packaged_task\n"; std::packaged_task<void()> f{std::move(lambda)}; std::cout << "#4: f()\n"; f(); #else // Otherwise, we need to turn our move-only function into one that can // be copied freely. There is no guarantee that it'll only be copied // once, so we resort to using a shared pointer. std::cout << "Moving to std::function\n"; std::function<void()> f{make_shared_fn(std::move(lambda))}; std::cout << "#4: f()\n"; f(); std::cout << "#5: f()\n"; f(); std::cout << "#6: f()\n"; f(); #endif } 

我已经把上面的程序放在了Coliru上 ,所以你可以运行和使用代码。

这是一些典型的输出…

 - &myPointer = 0xbfffe5c0, myPointer.get() = 0x7ae3cfd0, *myPointer = 42 Creating lambda - &myPointer = 0xbfffe5c0, myPointer.get() = 0x0 #1: lambda() - &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 42 - &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 2352 - &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 557424 #2: lambda() - &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 557424 - &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 31215744 - &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 3103164032 #3: lambda() - &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 3103164032 - &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 1978493952 - &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 751631360 Moving to std::function #4: f() - &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 751631360 - &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 3436650496 - &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2737348608 #5: f() - &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2737348608 - &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2967666688 - &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 3257335808 #6: f() - &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 3257335808 - &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 2022178816 - &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2515009536 

你会看到堆位置被重用,表明std::unique_ptr工作正常。 你也可以看到函数本身在我们把它存储到一个包装器中,而我们提供给std::function

如果我们切换到使用std::packaged_task ,它的最后一部分成为

 Moving to std::packaged_task #4: f() - &myPointerArg = 0xbfffe590, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 751631360 - &myPointerArg = 0xbfffe590, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 3436650496 - &myPointerArg = 0xbfffe590, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2737348608 

所以我们看到这个函数已经被移动了,而不是被移动到堆上,而是在堆栈中的std::packaged_task中。

希望这可以帮助!

我正在看这些答案,但我发现绑定很难阅读和理解。 所以我所做的就是创build一个移动拷贝的类。 这样就明确了它在做什么。

 #include <iostream> #include <memory> #include <utility> #include <type_traits> #include <functional> namespace detail { enum selection_enabler { enabled }; } #define ENABLE_IF(...) std::enable_if_t<(__VA_ARGS__), ::detail::selection_enabler> \ = ::detail::enabled // This allows forwarding an object using the copy constructor template <typename T> struct move_with_copy_ctor { // forwarding constructor template <typename T2 // Disable constructor for it's own type, since it would // conflict with the copy constructor. , ENABLE_IF( !std::is_same<std::remove_reference_t<T2>, move_with_copy_ctor>::value ) > move_with_copy_ctor(T2&& object) : wrapped_object(std::forward<T2>(object)) { } // move object to wrapped_object move_with_copy_ctor(T&& object) : wrapped_object(std::move(object)) { } // Copy constructor being used as move constructor. move_with_copy_ctor(move_with_copy_ctor const& object) { std::swap(wrapped_object, const_cast<move_with_copy_ctor&>(object).wrapped_object); } // access to wrapped object T& operator()() { return wrapped_object; } private: T wrapped_object; }; template <typename T> move_with_copy_ctor<T> make_movable(T&& object) { return{ std::forward<T>(object) }; } auto fn1() { std::unique_ptr<int, std::function<void(int*)>> x(new int(1) , [](int * x) { std::cout << "Destroying " << x << std::endl; delete x; }); return [y = make_movable(std::move(x))]() mutable { std::cout << "value: " << *y() << std::endl; return; }; } int main() { { auto x = fn1(); x(); std::cout << "object still not deleted\n"; x(); } std::cout << "object was deleted\n"; } 

move_with_copy_ctor类和它的辅助函数make_movable()可以处理任何可移动但不可复制的对象。 要访问包装对象,请使用operator()()

预期产出:

价值:1
对象仍然没有删除
价值:1
销毁000000DFDD172280
对象被删除

那么,指针地址可能会有所不同。 ;)

演示