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

No entiendo cuándo debería usar std::move y cuándo debo dejar que el comstackdor optimice … por ejemplo:

 using SerialBuffer = vector; // 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 ); } 

¿Qué debería usar?

Use 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 rvalue en una instrucción return precisamente cuando se permite la elisión de copia.

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

Todos los valores devueltos ya se han moved u optimizado, por lo que no es necesario moverlos explícitamente con los valores devueltos.

Los comstackdores pueden mover automáticamente el valor de retorno (para optimizar la copia) e incluso optimizar el movimiento.

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

Cuando se cumplen ciertos criterios, una implementación puede omitir la construcción de copia / movimiento de un objeto de clase, incluso si el constructor de copia / movimiento y / o el destructor para el objeto tienen efectos secundarios. En tales casos, la implementación trata la fuente 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 momento posterior en que los dos objetos habrían sido destruido sin la optimización. Esta elisión de las operaciones de copiar / mover, llamada copia elisión , está permitida en las siguientes circunstancias (que pueden combinarse para eliminar copias múltiples):

[…]

Ejemplo :

 class Thing { public: Thing(); ~Thing(); Thing(const Thing&); }; Thing f() { Thing t; return t; } Thing t2 = f(); 

Aquí los criterios para elisión se pueden combinar para eliminar dos llamadas al constructor de copia de la clase Thing : la copia del objeto automático local t en 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, la construcción del objeto local t puede verse como la inicialización directa del objeto global t2 , y la destrucción de ese objeto ocurrirá al salir del progtwig. Agregar un constructor de movimiento a Thing tiene el mismo efecto, pero es la construcción de movimiento del objeto temporal a t2 que se elimina. – ejemplo final ]

Cuando se cumplen o se cumplirían los criterios para elisión de una operación de copia, salvo 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 lvalue, la resolución de sobrecarga para seleccionar el constructor para la copia es primero se realizó como si el objeto fuera 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 del objeto (posiblemente cv-qualified), la resolución de sobrecarga se realiza nuevamente, considerando el objeto como un valor l.

Es bastante simple.

return buffer;

Si haces esto, entonces NRVO sucederá o no. Si no sucede, entonces se moverá el buffer .

return std::move( buffer );

Si haces esto, entonces NVRO no sucederá y se moverá el buffer .

Entonces, no hay nada que ganar usando std::move aquí, y mucho que perder.

Hay una excepción a esta regla:

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

Si el buffer es una referencia rvalue, entonces deberías usar std::move . Esto se debe a que las referencias no son elegibles para NRVO, por lo que sin std::move resultaría en una copia de un lvalue.

Esta es solo una instancia de la regla “siempre move las referencias rvalue y forward las referencias universales”, que tiene prioridad sobre la regla “nunca move un valor de retorno”.

Si devuelve una variable local, no use move() . Esto permitirá que el comstackdor use NRVO, y en su defecto, el comstackdor aún podrá realizar un movimiento (las variables locales se convierten en valores R dentro de una instrucción return ). Usar move() en ese contexto simplemente nhibernateía NRVO y forzaría al comstackdor a usar un movimiento (o una copia si el movimiento no está disponible). Si devuelve algo que no sea una variable local, NRVO no es una opción de todos modos y debe usar move() si (y solo si) tiene la intención de robar el objeto.