c++ - ¿Qué es std:: move(), y cuándo debería usarse?




c++11 move-semantics (4)

  1. ¿Qué es?
  2. ¿Qué hace?
  3. ¿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.


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

  1. 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).
  2. El constructor de movimiento se usa en lugar del constructor de copia, si el objeto tiene el tipo "rvalue-reference" ( Type && ).
  3. 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.





move-semantics