c++ - ¿Qué es std:: move(), y cuándo debería usarse?
c++11 move-semantics (6)
- ¿Qué es?
- ¿Qué hace?
- ¿Cuándo debería usarse?
Se aprecian buenos enlaces.
P: ¿Qué es std::move
?
A: std::move()
es una función de la biblioteca estándar de C ++ para convertir a una referencia rvalue.
Simplificadamente std::move(t)
es equivalente a:
static_cast<T&&>(t);
Un valor de r es un temporal que no persiste más allá de la expresión que lo define, como el resultado de una función intermedia que nunca se almacena en una variable.
int a = 3; // 3 is a rvalue, does not exist after expression is evaluated
int b = a; // a is a lvalue, keeps existing after expression is evaluated
Una implementación para std :: move () se proporciona en N2027: "Una breve introducción a las referencias de Rvalue" como sigue:
template <class T>
typename remove_reference<T>::type&&
std::move(T&& a)
{
return a;
}
Como puede ver, std::move
devuelve T&&
sin importar si se llama con un valor ( T
), tipo de referencia ( T&
) o referencia de valor ( T&&
).
Q: ¿Qué hace?
R: Como reparto, no hace nada durante el tiempo de ejecución. Solo es relevante en el momento de la compilación para decirle al compilador que desea continuar considerando la referencia como un valor.
foo(3 * 5); // obviously, you are calling foo with a temporary (rvalue)
int a = 3 * 5;
foo(a); // how to tell the compiler to treat `a` as an rvalue?
foo(std::move(a)); // will call `foo(int&& a)` rather than `foo(int a)` or `foo(int& a)`
Lo que no hace:
- Hacer una copia del argumento
- Llama al constructor de copia.
- Cambiar el objeto argumento
P: ¿Cuándo debería usarse?
R: Debería usar std::move
si desea llamar a funciones que soportan la semántica de movimiento con un argumento que no es un rvalue (expresión temporal).
Esto me plantea las siguientes preguntas de seguimiento:
¿Qué son las semánticas de movimiento? Mover la semántica en contraste con copiar la semántica es una técnica de programación en la que los miembros de un objeto se inicializan "tomando el control" en lugar de copiar los miembros de otro objeto. Tal ''toma de control'' solo tiene sentido con los punteros y los identificadores de recursos, que pueden transferirse de forma económica copiando el puntero o el identificador de enteros en lugar de los datos subyacentes.
¿Qué clase de clases y objetos soportan la semántica de movimientos? Depende de usted como desarrollador implementar la semántica de movimientos en sus propias clases si se beneficiarían de la transferencia de sus miembros en lugar de copiarlos. Una vez que implemente la semántica de movimiento, se beneficiará directamente del trabajo de muchos programadores de bibliotecas que han agregado soporte para manejar clases con la semántica de movimiento de manera eficiente.
¿Por qué el compilador no puede resolverlo por sí solo? El compilador no puede simplemente llamar a otra sobrecarga de una función a menos que usted lo diga. Debe ayudar al compilador a elegir si se debe llamar a la versión regular o de movimiento de la función.
¿En qué situaciones querría decirle al compilador que debería tratar una variable como un valor? Esto ocurrirá muy probablemente en las funciones de la plantilla o la biblioteca, donde se sabe que se podría recuperar un resultado intermedio.
1. "¿Qué es?"
Mientras que std::move()
es técnicamente una función, yo diría que no es realmente una función . Es una especie de convertidor entre las formas en que el compilador considera el valor de una expresión.
2. "¿Qué hace?"
Lo primero a tener en cuenta es que std::move()
realidad no mueve nada .
Si alguna vez has visto la serie de animación Bleach , es el equivalente al ablandamiento Reishi de Quincy Seele Schneider .
Sin embargo, en serio, convierte una expresión de ser un lvalue o rvalue puro (como una variable que podría estar usando por mucho tiempo, o un temporal que está transmitiendo por un tiempo, respectivamente) a un xvalue . Un xvalue le dice al compilador:
Puedes saquearme, mover todo lo que tengo y usarlo en otra parte (ya que de todos modos pronto seré destruido) ".
en otras palabras, cuando usas std::move(x)
, estás permitiendo que el compilador canibalice x
. Por lo tanto, si x
tiene, digamos, su propio búfer en la memoria, después de que std::move()
ing, el compilador puede tener otro objeto propio en su lugar.
3. "¿Cuándo debería usarse?"
Otra forma de hacer esta pregunta es "¿Para qué canibalizaría los recursos de un objeto existente?" Bueno, si está escribiendo el código de la aplicación, probablemente no esté jugando mucho con los objetos temporales creados por el compilador. Así que principalmente harías esto en lugares como constructores, métodos de operador, funciones similares al algoritmo STL, etc. donde los objetos se crean y se destruyen automáticamente. Por supuesto, eso es sólo una regla de oro.
Un uso típico es "mover" los recursos de un objeto a otro en lugar de copiarlos. @Guillaume enlaza con http://thbecker.net/articles/rvalue_references/section_01.html que tiene un ejemplo corto y sencillo: intercambiar dos objetos con menos copia.
template <class T>
swap(T& a, T& b) {
T tmp(a); // we now have two copies of a
a = b; // we now have two copies of b (+ discarded a copy of a)
b = tmp; // we now have two copies of tmp (+ discarded a copy of b)
}
El uso de move le permite intercambiar los recursos en lugar de copiarlos:
template <class T>
swap(T& a, T& b) {
T tmp(std::move(a));
a = std::move(b);
b = std::move(tmp);
}
Piense en lo que sucede cuando T es, digamos, vector<int>
de tamaño n. En la primera versión lees y escribes elementos 3 * n, en la segunda versión básicamente lees y escribes solo los 3 punteros a los buffers de vectores. Por supuesto, la clase T necesita saber cómo hacer el movimiento; debe tener un operador de asignación de movimiento y un constructor de movimiento para la clase T para que esto funcione.
Puede usar mover cuando necesite "transferir" el contenido de un objeto a otro lugar, sin hacer una copia (por ejemplo, el contenido no está duplicado, por eso podría usarse en algunos objetos no copiables, como unique_ptr). También es posible que un objeto tome el contenido de un objeto temporal sin hacer una copia (y ahorre mucho tiempo), con std :: move.
Este enlace realmente me ayudó:
http://thbecker.net/articles/rvalue_references/section_01.html
Lo siento si mi respuesta llega demasiado tarde, pero también estaba buscando un buen enlace para std :: move, y encontré que los enlaces de arriba son un poco "austeros".
Esto puso el énfasis en la referencia de valor r, en qué contexto debería usarlos, y creo que es más detallado, por eso quería compartir este enlace aquí.
std :: mover en sí mismo no hace mucho. Pensé que llamaba al constructor movido para un objeto, pero realmente solo realiza una conversión de tipo (convertir una variable lvalue a un valor r para que dicha variable pueda pasarse como un argumento a un constructor de movimiento u operador de asignación).
Así que std :: move solo se usa como un precursor del uso de la semántica de movimientos. Mover la semántica es esencialmente una forma eficiente de tratar con objetos temporales.
Considere el objeto A = B + C + D + E + F;
Este es un código bonito, pero E + F produce un objeto temporal. Entonces D + temp produce otro objeto temporal y así sucesivamente. En cada operador normal "+" de una clase, se producen copias en profundidad.
Por ejemplo
Object Object::operator+ (const Object& rhs) {
Object temp (*this);
// logic for adding
return temp;
}
La creación del objeto temporal en esta función es inútil: estos objetos temporales se eliminarán al final de la línea de todos modos a medida que queden fuera del alcance.
Más bien podemos usar la semántica de movimientos para "saquear" los objetos temporales y hacer algo como
Object& Object::operator+ (Object&& rhs) {
// logic to modify rhs directly
return rhs;
}
Esto evita que se hagan innecesarias copias en profundidad. Con referencia al ejemplo, la única parte donde se realiza una copia profunda es ahora E + F. El resto utiliza la semántica de movimientos. El constructor de movimiento u operador de asignación también debe implementarse para asignar el resultado a A.
Página de Wikipedia en C ++ 11 Referencias de valor R y constructores de movimiento
- En C ++ 11, además de copiar constructores, los objetos pueden tener constructores de movimiento.
(Y además de los operadores de asignación de copia, tienen operadores de asignación de movimiento). - El constructor de movimiento se usa en lugar del constructor de copia, si el objeto tiene el tipo "rvalue-reference" (
Type &&
). -
std::move()
es una conversión que produce una referencia rvalue a un objeto, para permitir que se mueva desde él.
Es una nueva forma en C ++ de evitar copias. Por ejemplo, al usar un constructor de movimientos, un std::vector
podría simplemente copiar su puntero interno a los datos del nuevo objeto, dejando el objeto movido en un estado incorrecto, evitando copiar todos los datos. Esto sería C ++ - válido.
Trate de buscar en Google para la semántica de movimiento, valor, reenvío perfecto.
What is it?
y What does it do?
Se ha explicado anteriormente.
Daré un ejemplo de when it should be used.
Por ejemplo, tenemos una clase con muchos recursos como una gran variedad en ella.
class ResHeavy{ // ResHeavy means heavy resource
public:
ResHeavy(int len=10):_upInt(new int[len]),_len(len){
cout<<"default ctor"<<endl;
}
ResHeavy(const ResHeavy& rhs):_upInt(new int[rhs._len]),_len(rhs._len){
cout<<"copy ctor"<<endl;
}
ResHeavy& operator=(const ResHeavy& rhs){
_upInt.reset(new int[rhs._len]);
_len = rhs._len;
cout<<"operator= ctor"<<endl;
}
ResHeavy(ResHeavy&& rhs){
_upInt = std::move(rhs._upInt);
_len = rhs._len;
rhs._len = 0;
cout<<"move ctor"<<endl;
}
// check array valid
bool is_up_valid(){
return _upInt != nullptr;
}
private:
std::unique_ptr<int[]> _upInt; // heavy array resource
int _len; // length of int array
};
Código de prueba:
void test_std_move2(){
ResHeavy rh; // only one int[]
// operator rh
// after some operator of rh, it becomes no-use
// transform it to other object
ResHeavy rh2 = std::move(rh); // rh becomes invalid
// show rh, rh2 it valid
if(rh.is_up_valid())
cout<<"rh valid"<<endl;
else
cout<<"rh invalid"<<endl;
if(rh2.is_up_valid())
cout<<"rh2 valid"<<endl;
else
cout<<"rh2 invalid"<<endl;
// new ResHeavy object, created by copy ctor
ResHeavy rh3(rh2); // two copy of int[]
if(rh3.is_up_valid())
cout<<"rh3 valid"<<endl;
else
cout<<"rh3 invalid"<<endl;
}
salida como abajo:
default ctor
move ctor
rh invalid
rh2 valid
copy ctor
rh3 valid
Podemos ver que std::move
with move constructor
hace que el recurso de transformación sea fácil.
¿Dónde más es std :: move útil?
std :: move también puede ser útil al ordenar una matriz de elementos. Muchos algoritmos de clasificación (como la ordenación por selección y la ordenación por burbuja) funcionan al intercambiar pares de elementos. Anteriormente, hemos tenido que recurrir a la semántica de copia para hacer el intercambio. Ahora podemos usar la semántica de movimientos, que es más eficiente.
También puede ser útil si queremos mover los contenidos administrados por un puntero inteligente a otro.
Citado :