c++ c++11 move forward

c++ - Uso de std:: forward vs std:: move



c++11 (3)

Siempre leo que std::forward es solo para usar con parámetros de plantilla. Sin embargo, me preguntaba por qué. Vea el siguiente ejemplo:

void ImageView::setImage(const Image& image){ _image = image; } void ImageView::setImage(Image&& image){ _image = std::move(image); }

Esas son dos funciones que básicamente hacen lo mismo; uno toma una referencia de valor l, el otro una referencia de valor r. Ahora, pensé que se supone que std::forward debe devolver una referencia de valor l si el argumento es una referencia de valor l y una referencia de valor r si el argumento es uno, este código podría simplificarse a algo como esto:

void ImageView::setImage(Image&& image){ _image = std::forward(image); }

Lo cual es similar al ejemplo que cplusplus.com menciona para std::forward (solo sin ningún parámetro de plantilla). Me gustaría saber si esto es correcto o no, y si no, por qué.

También me preguntaba cuál sería exactamente la diferencia para

void ImageView::setImage(Image& image){ _image = std::forward(image); }


Debe especificar el tipo de plantilla en std::forward .

En este contexto, Image&& image siempre es una referencia de valor r y std::forward<Image> siempre se moverá, por lo que también podría usar std::move .

Su función que acepta una referencia de valor r no puede aceptar valores l, por lo que no es equivalente a las dos primeras funciones.


No puede usar std::forward sin especificar explícitamente su argumento de plantilla. Se usa intencionalmente en un contexto no deducido.

Para comprender esto, debe comprender realmente cómo las referencias de reenvío ( T&& para una T deducida) funcionan internamente y no agitarlas como "es mágico". Así que echemos un vistazo a eso.

template <class T> void foo(T &&t) { bar(std::forward<T>(t)); }

Digamos que llamamos a foo así:

foo(42);

42 es un valor de tipo int . T se deduce a int . Por lo tanto, la llamada a la bar utiliza int como argumento de plantilla para std::forward . El tipo de retorno de std::forward<U> es U && . En este caso, eso es int && , por lo que t se reenvía como un valor r.

Ahora, llamemos a foo así:

int i = 42; foo(i);

i es un valor de tipo int . Debido a la regla especial para el reenvío perfecto, cuando se usa un valor de tipo V para deducir T en un parámetro de tipo T && , se usa V & para la deducción. Por lo tanto, en nuestro caso, T se deduce que es int & .

Por lo tanto, especificamos int & como argumento de plantilla para std::forward . Por lo tanto, su tipo de retorno será " int & && ", que se contrae en int & . Es un valor, así que i reenvían como un valor.

Resumen

Por qué esto funciona con plantillas es cuando haces std::forward<T> , T es a veces una referencia (cuando el original es un valor l) y otras veces no (cuando el original es un valor r). Por lo tanto, std::forward se convertirá en una referencia lvalue o rvalue según corresponda.

No puede hacer que esto funcione en la versión sin plantilla, precisamente porque solo tendrá un tipo disponible. Sin mencionar el hecho de que setImage(Image&& image) no aceptaría valores en absoluto: un valor no puede unirse a referencias de valor.


Recomiendo leer "Effective Modern C ++", su autor es Scott Meyers .

Tema 23: Comprenda std :: move y std :: forward.

Ítem ​​24: Distinguir referencias universales para referencias de valor.

Desde una perspectiva puramente técnica, la respuesta es sí: std :: forward puede hacerlo todo. std :: move no es necesario. Por supuesto, ninguna función es realmente necesaria, porque podríamos escribir elenco en todas partes, pero espero que estemos de acuerdo en que sería, bueno, asqueroso. Las atracciones de std :: move son conveniencia, menor probabilidad de error y mayor claridad .

rvalue-reference : esta función acepta rvalues ​​no puede aceptar lvalues.

void ImageView::setImage(Image&& image){ _image = std::forward(image); //error _image = std::move(image);//conventional _image = std::forward<Image>(image);//unconventional }

Tenga en cuenta primero que std :: move requiere solo un argumento de función, mientras que std :: forward requiere un argumento de función y un argumento de tipo plantilla.

template <typename T> void ImageView::setImage(T&& image){ _image = std::forward<T>(image); }

referencias universales (referencias de reenvío) : esta función acepta todo y realiza un reenvío perfecto.