c ++ 11 ¿Optimización del valor de retorno o movimiento? [duplicar]

Resuelto Elvis Dukaj asked hace 11 años • 4 respuestas

No entiendo cuándo debo usar std::movey cuándo debo dejar que el compilador optimice... por ejemplo:

using SerialBuffer = vector< unsigned char >;

// let compiler optimize it
SerialBuffer read( size_t size ) const
{
    SerialBuffer buffer( size );
    read( begin( buffer ), end( buffer ) );
    // Return Value Optimization
    return buffer;
}

// explicit move
SerialBuffer read( size_t size ) const
{
    SerialBuffer buffer( size );
    read( begin( buffer ), end( buffer ) );
    return move( buffer );
}

¿Cuál debo usar?

Elvis Dukaj avatar Jul 04 '13 22:07 Elvis Dukaj
Aceptado

Utilice exclusivamente el primer método:

Foo f()
{
  Foo result;
  mangle(result);
  return result;
}

Esto ya permitirá el uso del constructor de movimientos, si hay alguno disponible. De hecho, una variable local puede vincularse a una referencia de rvalue en una returndeclaración precisamente cuando se permite la elisión de copia.

Su segunda versión prohíbe activamente la elisión de copias. La primera versión es universalmente mejor.

Kerrek SB avatar Jul 04 '2013 15:07 Kerrek SB

Todos los valores de retorno ya están movedo están optimizados, por lo que no es necesario moverse explícitamente con los valores de retorno.

Los compiladores pueden mover automáticamente el valor de retorno (para optimizar la copia) ¡e incluso optimizar el movimiento!

Sección 12.8 del borrador del estándar n3337 (C++11):

Cuando se cumplen ciertos criterios, una implementación puede omitir la construcción de copiar/mover de un objeto de clase, incluso si el constructor y/o destructor de copiar/mover del objeto tiene efectos secundarios. En tales casos, la implementación trata el origen y el destino de la operación de copiar/mover omitida como simplemente dos formas diferentes de referirse al mismo objeto, y la destrucción de ese objeto ocurre en el último de los momentos en que los dos objetos habrían sido destruido sin la optimización. Esta elisión de operaciones de copiar/mover, llamada elisión de copia , está permitida en las siguientes circunstancias (que pueden combinarse para eliminar múltiples copias):

[...]

Ejemplo :

class Thing {
public:
Thing();
   ~Thing();
   Thing(const Thing&);
};

Thing f() {
   Thing t;
   return t;
}

Thing t2 = f();

Aquí los criterios de elisión se pueden combinar para eliminar dos llamadas al constructor de copia de la clase Thing: la copia del objeto automático local ten el objeto temporal para el valor de retorno de la función f() y la copia de ese objeto temporal en el objeto t2. Efectivamente, t se puede considerar que la construcción del objeto local inicializa directamente el objeto global t2, y la destrucción de ese objeto se producirá al salir del programa. Agregar un constructor de movimiento Thingtiene el mismo efecto, pero es la construcción de movimiento desde el objeto temporal la t2que se omite. - fin del ejemplo ]

Cuando se cumplen o se cumplirían los criterios para la elisión de una operación de copia, excepto por el hecho de que el objeto fuente es un parámetro de función y el objeto que se va a copiar está designado por un valor l, la resolución de sobrecarga para seleccionar el constructor para la copia es se realizó por primera vez como si el objeto estuviera designado por un valor r. Si la resolución de sobrecarga falla, o si el tipo del primer parámetro del constructor seleccionado no es una referencia rvalue al tipo de objeto (posiblemente calificado por cv), la resolución de sobrecarga se realiza nuevamente, considerando el objeto como un valor l.

Jamin Grey avatar Jul 04 '2013 15:07 Jamin Grey

Es bastante simple.

return buffer;

Si se hace esto, entonces se producirá la NRVO o no. Si no sucede, bufferserá trasladado.

return std::move( buffer );

Si hace esto, la NVRO no se producirá y bufferserá eliminada.

Así que no hay nada que ganar usándolo std::moveaquí y mucho que perder.


Hay una excepción* a la regla anterior:

Buffer read(Buffer&& buffer) {
    //...
    return std::move( buffer );
}

Si bufferes una referencia de valor, entonces deberías usar std::move. Esto se debe a que las referencias no son elegibles para NRVO, por lo que sin std::moveellas se obtendría una copia de un valor l.

Este es solo un ejemplo de la regla "siempre movereferencias de valor y forwardreferencias universales", que tiene prioridad sobre la regla "nunca moveun valor de retorno".

* A partir de C++20 esta excepción se puede olvidar. Las referencias a Rvalue en returnlas declaraciones se mueven implícitamente desde ahora.

Oktalist avatar Mar 18 '2015 17:03 Oktalist

Si devuelve una variable local, no utilice move(). Esto permitirá que el compilador use NRVO y, en su defecto, el compilador aún podrá realizar un movimiento (las variables locales se convierten en valores R dentro de una returndeclaración). Usarlo move()en ese contexto simplemente inhibiría NRVO y obligaría al compilador a usar un movimiento (o una copia si el movimiento no está disponible). Si estás devolviendo algo que no sea una variable local, NRVO no es una opción de todos modos y debes usarlo move()si (y sólo si) tienes la intención de robar el objeto.

Adam H. Peterson avatar Jul 04 '2013 15:07 Adam H. Peterson