c++ lambda c++11 rvalue-reference

c++ - Mover captura en lambda



c++11 rvalue-reference (4)

Captura de lambda generalizada en C ++ 14

En C ++ 14 tendremos la llamada captura lambda generalizada . Esto permite la captura de movimiento. El siguiente será el código legal en 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 ); } );

Pero es mucho más general en el sentido de que las variables capturadas se pueden inicializar con algo así:

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

En C ++ 11 esto no es posible aún, pero con algunos trucos que involucran tipos de ayuda. Afortunadamente, el compilador Clang 3.4 ya implementa esta increíble función. El compilador se lanzará en diciembre de 2013 o enero de 2014, si se mantiene el ritmo de publicación reciente .

ACTUALIZACIÓN: El compilador Clang 3.4 fue lanzado el 6 de enero de 2014 con dicha característica.

Una solución para la captura de movimientos

Aquí hay una implementación de una función auxiliar make_rref que ayuda con la captura de movimiento artificial

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

Y aquí hay un caso de prueba para esa función que funcionó con éxito en mi 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() ); }

El inconveniente aquí es que lambda es copiable y cuando se copia, la aserción en el constructor de copia de rref_impl falla y da lugar a un error en el tiempo de ejecución. La siguiente podría ser una solución mejor e incluso más genérica porque el compilador detectará el error.

Emulación de captura lambda generalizada en C ++ 11

Aquí hay una idea más sobre cómo implementar la captura lambda generalizada. El uso de la capture() función capture() (cuya implementación se encuentra más abajo) es la siguiente:

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

Aquí lambda es un objeto funtor (casi un lambda real) que ha capturado std::move(p) medida que se pasa a capture() . El segundo argumento de capture es un lambda que toma la variable capturada como un argumento. Cuando lambda se utiliza como un objeto de función, todos los argumentos que se le pasan se reenviarán a la lambda interna como argumentos después de la variable capturada. (En nuestro caso, no hay más argumentos para ser enviados). Básicamente, sucede lo mismo que en la solución anterior. Así es como se implementa la 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) ); }

Esta segunda solución también es más limpia, ya que desactiva la copia de la lambda, si el tipo capturado no se puede copiar. En la primera solución que solo se puede verificar en tiempo de ejecución con un assert() .

¿Cómo capturo por movimiento (también conocido como referencia rvalue) en una lambda C ++ 11?

Estoy tratando de escribir algo como esto:

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


Estaba viendo estas respuestas, pero me pareció difícil de leer y entender. Entonces, lo que hice fue crear una clase que se moviera en copia. De esta manera, es explícito con lo que está haciendo.

#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"; }

La clase move_with_copy_ctor y su función auxiliar make_movable() funcionarán con cualquier objeto móvil pero no se puede copiar. Para obtener acceso al objeto envuelto, use el operator()() .

Rendimiento esperado:

value: 1 object still not deleted value: 1 Destroying 000000DFDD172280 object was deleted

Bueno, la dirección del puntero puede variar. ;)

Demo


Puede lograr la mayor parte de lo que desea usando std::bind , así:

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

El truco aquí es que en lugar de capturar su objeto de solo movimiento en la lista de capturas, lo convertimos en un argumento y luego usamos una aplicación parcial a través de std::bind para que desaparezca. Tenga en cuenta que el lambda lo toma por referencia , porque en realidad está almacenado en el objeto de vinculación. También agregué un código que escribe en el objeto móvil real, porque eso es algo que podría querer hacer.

En C ++ 14, puede usar la captura lambda generalizada para lograr los mismos fines, con este código:

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

Pero este código no te compra nada que no tengas en C ++ 11 a través de std::bind . (Hay algunas situaciones donde la captura lambda generalizada es más poderosa, pero no en este caso).

Ahora solo hay un problema; quería poner esta función en una función std::function , pero esa clase requiere que la función sea CopyConstructible , pero no lo es, solo MoveConstructible porque está almacenando un std::unique_ptr que no es CopyConstructible .

Puede solucionar el problema con la clase contenedora y otro nivel de direccionamiento indirecto, pero quizás no necesite std::function en absoluto. Dependiendo de sus necesidades, es posible que pueda usar std::packaged_task ; Haría el mismo trabajo que std::function , pero no requiere que la función sea copiable, solo se puede mover (de manera similar, std::packaged_task solo se puede mover). La desventaja es que, debido a que está destinado a usarse junto con std :: future, solo puede llamarlo una vez.

Aquí hay un programa corto que muestra todos estos conceptos.

#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, e.g., 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 }

He puesto el programa anterior en Coliru , para que pueda correr y jugar con el código.

Aquí hay algunos resultados típicos ...

- &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

Puede ver cómo se vuelven a utilizar ubicaciones de std::unique_ptr dinámico, lo que demuestra que std::unique_ptr funciona correctamente. También ves que la función se mueve cuando la guardamos en un contenedor que alimentamos a std::function .

Si cambiamos a usar std::packaged_task , se convierte en la última parte

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

así que vemos que la función se ha movido, pero en lugar de moverse al montón, está dentro de la std::packaged_task que está en la pila.

¡Espero que esto ayude!


También podría usar std::bind para capturar el unique_ptr :

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