Captura generalizada de lambda en C ++ 14
En C ++ 14 tendremos la llamada captura lambda generalizada . Esto permite la captura de movimiento. El siguiente será un 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 aún no es posible, pero con algunos trucos que involucran tipos de ayuda. Afortunadamente, el compilador Clang 3.4 ya implementa esta increíble característica. El compilador se lanzará en diciembre de 2013 o enero de 2014, si se mantiene el ritmo de lanzamiento reciente .
ACTUALIZACIÓN: El compilador Clang 3.4 se lanzó el 6 de enero de 2014 con dicha función.
Una solución alternativa 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 se ejecutó 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 afirmación en el constructor de copias de rref_impl
fallas que conducen a un error de tiempo de ejecución. Lo siguiente podría ser una solución mejor y aún 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 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
hay un objeto functor (casi una lambda real) que se ha capturado a std::move(p)
medida que se pasa capture()
. El segundo argumento de capture
es una lambda que toma la variable capturada como argumento. Cuando lambda
se usa como un objeto de función, todos los argumentos que se le pasan se enviarán a la lambda interna como argumentos después de la variable capturada. (En nuestro caso no hay más argumentos que enviar). Esencialmente, sucede lo mismo que en la solución anterior. Aquí se explica cómo capture
se implementa:
#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 deshabilita la copia de 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()
.
moveCapture
contenedor para pasarlos como argumentos (este método se usa arriba y en Capn'Proto, una biblioteca del creador de protobuffs) o simplemente acepte que necesita compiladores que lo admitan: PTambién puede usar
std::bind
para capturarunique_ptr
:fuente
unique_ptr
referencia de valor no puede unirse a unint *
.myPointer
en este caso). Por lo tanto, el código anterior no se compila en VS2013. Sin embargo, funciona bien en GCC 4.8.Puede lograr la mayor parte de lo que quiere usar
std::bind
, así: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
std::bind
para que desaparezca. Tenga en cuenta que el lambda lo toma por referencia , porque en realidad está almacenado en el objeto de enlace. También agregué código que escribe en el objeto móvil real, porque eso es algo que es posible que desee hacer.En C ++ 14, puede usar la captura lambda generalizada para lograr los mismos fines, con este código:
Pero este código no le compra nada que no tenía en C ++ 11 a través de
std::bind
. (Hay algunas situaciones en las que 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 a
std::function
, pero esa clase requiere que la función sea CopyConstructible , pero no lo es, solo es MoveConstructible porque está almacenando unastd::unique_ptr
que no es CopyConstructible .Debe solucionar el problema con la clase wrapper y otro nivel de indirección, pero tal vez no lo necesite
std::function
en absoluto. Dependiendo de sus necesidades, puede usarstd::packaged_task
; haría el mismo trabajo questd::function
, pero no requiere que la función sea copiable, solo móvil (de manera similar,std::packaged_task
solo es móvil). La desventaja es que debido a que está destinado a usarse junto con std :: future, solo puede llamarlo una vez.Aquí hay un breve programa que muestra todos estos conceptos.
Puse el programa anterior en Coliru , para que pueda ejecutar y jugar con el código.
Aquí hay una salida típica ...
Puede ver las ubicaciones del montón que se reutilizan, lo que muestra que
std::unique_ptr
funciona correctamente. También verá que la función en sí se mueve cuando la guardamos en un contenedor al que alimentamosstd::function
.Si cambiamos a usar
std::packaged_task
, la última parte se convierte enasí que vemos que la función se ha movido, pero en lugar de moverse al montón, está dentro
std::packaged_task
de la pila.¡Espero que esto ayude!
fuente
Tarde, pero como algunas personas (incluyéndome a mí) todavía están atrapados en c ++ 11:
Para ser honesto, no me gusta ninguna de las soluciones publicadas. Estoy seguro de que funcionarán, pero requieren muchas cosas adicionales y / o
std::bind
sintaxis críptica ... y no creo que valga la pena el esfuerzo para una solución tan temporal que se refactorizará de todos modos al actualizar a c ++> = 14. Así que creo que la mejor solución es evitar la captura de movimientos para c ++ 11 por completo.Por lo general, la solución más simple y mejor legible es usar
std::shared_ptr
, que son copiables y, por lo tanto, el movimiento es completamente evitable. Lo malo es que es un poco menos eficiente, pero en muchos casos la eficiencia no es tan importante..
Si ocurre un caso muy raro, que es realmente obligatorio para
move
el puntero (por ejemplo, si desea eliminar explícitamente un puntero en un hilo separado debido a la larga duración de eliminación, o el rendimiento es absolutamente crucial), ese es prácticamente el único caso en el que todavía uso punteros en bruto en c ++ 11. Estos, por supuesto, también se pueden copiar.Por lo general, marco estos casos raros con un
//FIXME:
para garantizar que se refactorice una vez que se actualice a c ++ 14.Sí, los punteros en bruto están bastante mal vistos en estos días (y no sin razón), pero realmente creo que en estos casos raros (¡y temporales!) Son la mejor solución.
fuente
Estaba mirando estas respuestas, pero me pareció difícil 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.
La
move_with_copy_ctor
clase y su función auxiliar funcionaránmake_movable()
con cualquier objeto móvil pero no copiable. Para obtener acceso al objeto envuelto, use eloperator()()
.Rendimiento esperado:
Bueno, la dirección del puntero puede variar. ;)
Demo
fuente
Esto parece funcionar en gcc4.8
fuente