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.