c++ - ¿Cómo transfiere std:: move() valores a RValues?
c++11 move-semantics (2)
Simplemente me encontré no comprendiendo completamente la lógica de std::move()
.
Al principio, 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 en la definición de std::move()
en VS2010, sigue siendo confuso.
la definición de std :: move () va debajo.
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 se ve a continuación,
// main()
Object obj1;
Object obj2 = std::move(obj1);
básicamente es igual a
// std::move()
_Ty&& _Arg = Obj1;
Pero como ya sabes, no puedes vincular directamente un LValue a una referencia 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 comprender completamente cómo funciona esto, debería echarle 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 habilidades básicas 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á increíble).
Comenzamos con la función de movimiento (que limpié un poco):
template <typename T>
typename remove_reference<T>::type&& move(T&& arg)
{
return static_cast<typename remove_reference<T>::type&&>(arg);
}
Comencemos con la parte más fácil, es decir, cuando la función se llama con rvalue:
Object a = std::move(Object());
// Object() is temporary, which is prvalue
y nuestra plantilla de move
se instancia de la siguiente manera:
// move with [T = Object]:
remove_reference<Object>::type&& move(Object&& arg)
{
return static_cast<remove_reference<Object>::type&&>(arg);
}
Como remove_reference
convierte T&
T
o T&&
en T
, y Object
no es referencia, nuestra función final es:
Object&& move(Object&& arg)
{
return static_cast<Object&&>(arg);
}
Ahora, te preguntarás: ¿necesitamos el elenco? La respuesta es: sí, lo hacemos. La razón es simple; la referencia de rvalue nombrada se trata como lvalue (y la conversión implícita de lvalue a rvalue reference está prohibida por estándar).
Esto es lo que sucede cuando llamamos move
con lvalue:
Object a; // a is lvalue
Object b = std::move(a);
y ejemplificación de move
correspondiente:
// move with [T = Object&]
remove_reference<Object&>::type&& move(Object& && arg)
{
return static_cast<remove_reference<Object&>::type&&>(arg);
}
De nuevo, remove_reference
convierte Object&
to Object
y obtenemos:
Object&& move(Object& && arg)
{
return static_cast<Object&&>(arg);
}
Ahora llegamos a la parte complicada: ¿qué significa Object& &&
even y cómo puede unirse a lvalue?
Para permitir el reenvío perfecto, el estándar C ++ 11 proporciona reglas especiales para el colapso de referencia, que son las siguientes:
Object & & = Object &
Object & && = Object &
Object && & = Object &
Object && && = Object &&
Como puede ver, bajo estas reglas, Object& &&
realidad significa Object&
, que es una referencia lvalue simple que permite vincular lvalues.
La función final es así:
Object&& move(Object& arg)
{
return static_cast<Object&&>(arg);
}
que no es diferente de la instanciación previa con rvalue; ambos lanzan su argumento a la referencia de valor real y luego la devuelven. La diferencia es que la primera instanciación se puede usar solo con valores r, mientras que la segunda funciona con valores l.
Para explicar por qué necesitamos remove_reference
un poco más, remove_reference
esta función
template <typename T>
T&& wanna_be_move(T&& arg)
{
return static_cast<T&&>(arg);
}
y crear una instancia con lvalue.
// wanna_be_move [with T = Object&]
Object& && wanna_be_move(Object& && arg)
{
return static_cast<Object& &&>(arg);
}
Aplicando las reglas de colapso de referencia mencionadas anteriormente, puede ver que obtenemos una función que no se puede utilizar como move
(para decirlo simplemente, lo llama con lvalue, obtiene lvalue de nuevo). En todo caso, esta función es la función de identidad.
Object& wanna_be_move(Object& arg)
{
return static_cast<Object&>(arg);
}
_Ty es un parámetro de plantilla, y en esta situación
Object obj1;
Object obj2 = std::move(obj1);
_Ty es tipo "Object &"
por eso es necesario _Remove_reference.
Sería más como
typedef Object& ObjectRef;
Object obj1;
ObjectRef&& obj1_ref = obj1;
Object&& obj2 = (Object&&)obj1_ref;
Si no eliminamos la referencia, sería como si estuviéramos haciendo
Object&& obj2 = (ObjectRef&&)obj1_ref;
Pero ObjectRef && reduce a Object &, que no pudimos enlazar con obj2.
La razón por la que se reduce de esta manera es para apoyar el reenvío perfecto. Vea este papel .