¿Cuál es la diferencia entre std :: move y std :: forward

Vi esto aquí: Move Constructor llamando a la clase base Move Constructor

Podría alguien explicar:

  1. la diferencia entre std::move y std::forward , preferiblemente con algunos ejemplos de código?
  2. Cómo pensarlo fácilmente, y cuándo usarlo

std::move toma un objeto y le permite tratarlo como temporal (un valor r). Aunque no es un requisito semántico, por lo general, una función que acepte una referencia a un valor r lo invalidará. Cuando ve std::move , indica que el valor del objeto no se debe usar después, pero aún puede asignar un nuevo valor y continuar usándolo.

std::forward tiene un único caso de uso: para convertir un parámetro de función con plantilla (dentro de la función) en la categoría de valor (lvalue o rvalue) que la persona que llama utilizó para pasarlo. Esto permite que los argumentos rvalue pasen como valores r, y lvalues ​​para pasarlos como lvalues, un esquema llamado “reenvío perfecto”.

Para ilustrar :

 void overloaded( int const &arg ) { std::cout << "by lvalue\n"; } void overloaded( int && arg ) { std::cout << "by rvalue\n"; } template< typename t > /* "t &&" with "t" being template param is special, and adjusts "t" to be (for example) "int &" or non-ref "int" so std::forward knows what to do. */ void forwarding( t && arg ) { std::cout << "via std::forward: "; overloaded( std::forward< t >( arg ) ); std::cout << "via std::move: "; overloaded( std::move( arg ) ); // conceptually this would invalidate arg std::cout << "by simple passing: "; overloaded( arg ); } int main() { std::cout << "initial caller passes rvalue:\n"; forwarding( 5 ); std::cout << "initial caller passes lvalue:\n"; int x = 5; forwarding( x ); } 

Como Howard menciona, también hay similitudes ya que ambas funciones simplemente se convierten en tipo de referencia. Pero fuera de estos casos de uso específicos (que cubren el 99,9% de la utilidad de los moldes de referencia rvalue), debe usar static_cast directamente y escribir una buena explicación de lo que está haciendo.

Tanto std::forward como std::move no son más que moldes.

 X x; std::move(x); 

Lo anterior arroja la expresión lvalue x del tipo X a una expresión rvalue de tipo X (un xvalor para ser exacto). move también puede aceptar un valor r:

 std::move(make_X()); 

y en este caso es una función de identidad: toma un valor r de tipo X y devuelve un valor r de tipo X.

Con std::forward puede seleccionar el destino hasta cierto punto:

 X x; std::forward(x); 

Lanza la expresión lvalue x del tipo X a una expresión de tipo Y. Hay restricciones sobre lo que Y puede ser.

Y puede ser una Base accesible de X, o una referencia a una Base de X. Y puede ser X, o una referencia a X. Uno no puede rechazar cv-qualifiers con forward , pero uno puede agregar cv-qualifiers. Y no puede ser un tipo que sea meramente convertible desde X, excepto a través de una conversión base accesible.

Si Y es una referencia lvalue, el resultado será una expresión lvalue. Si Y no es una referencia lvalue, el resultado será una expresión rvalue (xvalue para ser preciso).

forward puede tomar un argumento rvalue solo si Y no es una referencia lvalue. Es decir, no puedes convertir un valor r en lvalue. Esto es por razones de seguridad, ya que hacerlo generalmente lleva a referencias pendientes. Pero lanzar un rvalue a rvalue está bien y permitido.

Si intenta especificar Y a algo que no está permitido, el error se detectará en tiempo de comstackción, no de tiempo de ejecución.

std::forward se utiliza para reenviar un parámetro exactamente de la manera en que se pasó a una función. Como se muestra aquí:

Cuándo usar std :: forward para reenviar argumentos?

El uso de std::move ofrece un objeto como un valor r, para posiblemente coincidir con un constructor de movimiento o una función que acepte valores de r. Lo hace para std::move(x) incluso si x no es un valor por sí mismo.