c++ c++11 move-semantics rvo

c++ - ¿Por qué std:: move previene RVO?



c++11 move-semantics (2)

En muchos casos, al devolver un local desde una función, RVO entra en acción. Sin embargo, pensé que el uso explícito de std::move al menos impondría la mudanza cuando no ocurra el RVO, pero ese RVO aún se aplica cuando es posible. Sin embargo, parece que este no es el caso.

#include "iostream" class HeavyWeight { public: HeavyWeight() { std::cout << "ctor" << std::endl; } HeavyWeight(const HeavyWeight& other) { std::cout << "copy" << std::endl; } HeavyWeight(HeavyWeight&& other) { std::cout << "move" << std::endl; } }; HeavyWeight MakeHeavy() { HeavyWeight heavy; return heavy; } int main() { auto heavy = MakeHeavy(); return 0; }

Probé este código con VC ++ 11 y GCC 4.71, configuración de depuración y liberación ( -O2 ). El copiador nunca se llama. El movimiento ctor solo lo llama VC ++ 11 en la configuración de depuración. En realidad, todo parece estar bien con estos compiladores en particular, pero que yo sepa, RVO es opcional.

Sin embargo, si utilizo explícitamente move :

HeavyWeight MakeHeavy() { HeavyWeight heavy; return std::move(heavy); }

el movimiento ctor siempre se llama. Así que tratar de hacerlo "seguro" lo empeora.

Mis preguntas son:
- ¿Por qué std::move previene RVO?
- ¿Cuándo es mejor "esperar lo mejor" y confiar en RVO, y cuándo debo usar explícitamente std::move ? O, en otras palabras, ¿cómo puedo dejar que la optimización del compilador haga su trabajo y aún aplicar mover si no se aplica RVO?


¿Cómo puedo dejar que la optimización del compilador haga su trabajo y seguir haciendo movimientos si no se aplica RVO?

Me gusta esto:

HeavyWeight MakeHeavy() { HeavyWeight heavy; return heavy; }

Transformar el regreso en un movimiento es obligatorio.


Los casos en los que se permite elisión de copia y movimiento se encuentran en la sección 12.8 §31 de la Norma (versión N3690):

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 seleccionado para la operación de copiar / mover 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):

  • en una instrucción de return en una función con un tipo de retorno de clase, cuando la expresión es el nombre de un objeto automático no volátil (que no sea una función o parámetro catch-clause) con el mismo tipo cv-no calificado que el tipo de retorno de función, la operación copiar / mover se puede omitir construyendo el objeto automático directamente en el valor de retorno de la función
  • [...]
  • cuando un objeto de clase temporal que no ha sido vinculado a una referencia (12.2) se copiará / moverá a un objeto de clase con el mismo tipo cv no calificado, la operación de copiar / mover se puede omitir construyendo el objeto temporal directamente en el objetivo de la copia / movimiento omitido
  • [...]

(Los dos casos que dejé se refieren al caso de tirar y atrapar objetos de excepción que considero menos importantes para la optimización).

Por lo tanto, en una declaración de retorno, la elisión de copia solo puede ocurrir, si la expresión es el nombre de una variable local. Si escribe std::move(var) , ya no es el nombre de una variable. Por lo tanto, el compilador no puede eludir el movimiento, si debe cumplir con el estándar.

Stephan T. Lavavej habló sobre esto en Going Native 2013 y explicó exactamente su situación y por qué evitar std::move() aquí. Comienza a mirar en el minuto 38:04. Básicamente, cuando se devuelve una variable local del tipo de retorno, generalmente se trata como un valor r, lo que permite moverse de manera predeterminada.