Me encontré sin comprender completamente la lógica de std::move()
.
Al principio, lo busqué en Google, pero parece que solo hay documentos sobre cómo usar std::move()
, no cómo funciona su estructura.
Quiero decir, sé cuál es la función del miembro de la plantilla, pero cuando miro std::move()
definición en VS2010, todavía es confuso.
la definición de std :: move () va más abajo.
template<class _Ty> inline
typename tr1::_Remove_reference<_Ty>::_Type&&
move(_Ty&& _Arg)
{ // forward _Arg as movable
return ((typename tr1::_Remove_reference<_Ty>::_Type&&)_Arg);
}
Lo que es extraño primero para mí es el parámetro, (_Ty && _Arg), porque cuando llamo a la función como ves a continuación,
// main()
Object obj1;
Object obj2 = std::move(obj1);
Básicamente equivale a
// std::move()
_Ty&& _Arg = Obj1;
Pero como ya sabes, no puedes vincular directamente un LValue a una referencia de RValue, lo que me hace pensar que debería ser así.
_Ty&& _Arg = (Object&&)obj1;
Sin embargo, esto es absurdo porque std :: move () debe funcionar para todos los valores.
Así que supongo que para entender completamente cómo funciona esto, debería echar un vistazo a estas estructuras también.
template<class _Ty>
struct _Remove_reference
{ // remove reference
typedef _Ty _Type;
};
template<class _Ty>
struct _Remove_reference<_Ty&>
{ // remove reference
typedef _Ty _Type;
};
template<class _Ty>
struct _Remove_reference<_Ty&&>
{ // remove rvalue reference
typedef _Ty _Type;
};
Desafortunadamente, sigue siendo tan confuso y no lo entiendo.
Sé que todo esto se debe a mi falta de conocimientos básicos de sintaxis sobre C ++. Me gustaría saber cómo funcionan a fondo y cualquier documento que pueda obtener en Internet será más que bienvenido. (Si puedes explicar esto, también será genial).
fuente
move
funciona en lugar de cómo se implementa. Encuentro esta explicación realmente útil: pagefault.blog/2018/03/01/… .Respuestas:
Comenzamos con la función de movimiento (que limpié un poco):
Comencemos con la parte más fácil, es decir, cuando se llama a la función con rvalue:
y nuestra
move
plantilla se crea una instancia de la siguiente manera:Dado que se
remove_reference
convierteT&
enT
oT&&
enT
, yObject
no es una referencia, nuestra función final es:Ahora, podría preguntarse: ¿necesitamos siquiera el yeso? La respuesta es: sí, lo hacemos. La razón es simple; la referencia rvalue nombrada se trata como lvalue (y la conversión implícita de la referencia lvalue a rvalue está prohibida por estándar).
Esto es lo que sucede cuando llamamos
move
con lvalue:y la correspondiente
move
instanciación:Una vez más,
remove_reference
convertidosObject&
aObject
y obtenemos:Ahora llegamos a la parte complicada: ¿qué significa
Object& &&
incluso y cómo se puede vincular a lvalue?Para permitir un reenvío perfecto, el estándar C ++ 11 proporciona reglas especiales para el colapso de referencias, que son las siguientes:
Como puede ver, bajo estas reglas
Object& &&
realmente significaObject&
, que es una referencia lvalue simple que permite vincular lvalues.La función final es así:
que no es diferente de la instanciación anterior con rvalue: ambos lanzan su argumento a la referencia rvalue y luego lo devuelven. La diferencia es que la primera instanciación se puede usar solo con rvalues, mientras que la segunda funciona con lvalues.
Para explicar por qué necesitamos
remove_reference
un poco más, probemos esta funcióny instanciarlo con lvalue.
Aplicando las reglas de colapso de referencia mencionadas anteriormente, puede ver que obtenemos una función que no se puede usar como
move
(para decirlo de manera simple, la llama con lvalue, obtiene lvalue de vuelta). En todo caso, esta función es la función de identidad.fuente
T
comoObject&
, no sabía que esto estaba realmente hecho. Hubiera esperadoT
evaluar tambiénObject
en este caso, ya que pensé que esta era la razón para introducir referencias de contenedor ystd::ref
, o no lo era.template <typename T> void f(T arg)
(que es lo que está en el artículo de Wikipedia) ytemplate <typename T> void f(T& arg)
. El primero se resuelve en valor (y si desea pasar la referencia, debe ajustarlostd::ref
), mientras que el segundo siempre se resuelve en referencia. Lamentablemente, las reglas para la deducción de argumentos de plantilla son bastante complejas, por lo que no puedo proporcionar un razonamiento preciso por qué seT&&
resuelveObject& &&
(pero sucede de hecho).template <typename T> T&& also_wanna_be_move(T& arg) { return static_cast<T&&>(arg); }
std::move
solo desea convertir lvalues a rvalues, entonces sí,T&
estaría bien. Este truco se hace principalmente por flexibilidad: puede llamarstd::move
a todo (rvalues incluidos) y recuperar rvalue._Ty es un parámetro de plantilla, y en esta situación
_Ty es del tipo "Objeto &"
por eso es necesario _Remove_reference.
Seria mas como
Si no eliminamos la referencia, sería como lo estábamos haciendo
Pero ObjectRef && se reduce a Object &, que no pudimos vincular a obj2.
La razón por la que se reduce de esta manera es para admitir el reenvío perfecto. Vea este documento .
fuente
_Remove_reference_
es necesario. Por ejemplo, si tieneObject&
como typedef, y toma una referencia a él, todavía obtieneObject&
. ¿Por qué no funciona con &&? No es una respuesta a eso, y tiene que ver con perfecta reenvío.