2011-10-21 35 views
8

He estado aprendiendo acerca de los constructores de movimientos durante el último día, tratando de cumplir con una regla general de devolver por valor como la mayoría de la gente parece sugerir, y he encontrado una interesante (yo) dilema.RVO, mover operaciones y un dilema

Supongamos que tengo un costoso para construir/copiar la clase 'C' que ha definido correctamente el constructor de copia, el operador de asignación, el constructor de movimientos y el operador de asignación de movimiento.

primer lugar, esta pieza de código elude el constructor de copia como esperaba:

C make_c1() { 
    return C(); 
} 

como lo hace esto:

C make_c2() { 
    C tmp; 
    return tmp; 
} 

y lo mismo ocurre esto (si yo paso en un 1 o 2) :

C make_c3(int a) { 
    return a == 1 ? make_c1() : make_c2(); 
} 

es cuando me llegar a este que tengo un problema:

C make_c4(int a) { 
    C tmp; 
    return a == 1 ? make_c1() : tmp; 
} 

Al pasar en 1 se dispara RVO para el resultado de make_c1, pero al pasar en 2 se activa el constructor de copias en tmp.

de Modificación de la función a la siguiente provoca el movimiento constructor para ser activado para tmp en su lugar:

C make_c5(int a) { 
    C tmp; 
    return a == 1 ? make_c1() : std::move(tmp); 
} 

Todos grande y maravilloso, excepto ...

En estos ejemplos simples, RVO se ha disparado bastante tanto como esperaba.

Sin embargo, ¿qué pasa si mi código es un poco más complejo y en algunos compiladores no evoca RVO en esa última función? En ese caso, tendría que ajustar mi llamada a make_c1 en std :: move, lo que hará que el código sea menos eficiente en los compiladores que evocan a RVO.

Así que mis preguntas son:

  1. ¿Por qué el movimiento constructor no invoca en make_c4 cuando regresé mi objeto local? (Está a punto de ser destruido después de todo).
  2. En la función make_c5, ¿debo devolver los resultados de make_c1 por valor o moverlos? (Para evitar diferentes versiones del código para diferentes compiladores/plataformas).
  3. ¿Existe alguna forma mejor de codificar la función final para que haga lo correcto para una implementación razonable del compilador?

El compilador con el que he estado jugando es GCC 4.5.3 en Cygwin.

Respuesta

7

El movimiento de devolución implícito solo es legal en los mismos contextos en los que RVO es legal. Y RVO es legal 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 devolución de función ([class.copy]/p31/b1)

Si a transformar a make_c4:

C make_c4(int a) { 
    C tmp; 
    if (a == 1) 
     return make_c1(); 
    return tmp; 
} 

se obtiene la medida que se espera la construcción de la llamada a make_c4(2).Su reescritura make_c5 no es deseable por exactamente las razones que indica.

Actualización:

I debería haber incluido también una referencia a [expr.cond]/p6/b1 que explica la semántica de la expresión condicional cuando la segunda expresión es una prvalue y la tercera es una lvalue , pero ambos tienen el mismo tipo:

El segundo y tercer operandos tienen el mismo tipo; el resultado es de ese tipo. Si los operandos tienen un tipo de clase, el resultado es un prvalue temporal del tipo de resultado, que se inicializa desde el segundo operando o el tercer operando según el valor del primer operando .

I.e. este párrafo especifica que el prvalue resultante del condicional es copiado inicializado, del tercer argumento en su ejemplo. La inicialización de la copia se define en [dcl.init]/p14. Cuando la fuente de una inicialización de copia es un valor l de clase, esto invocará el constructor de copia del tipo. Si la fuente es un valor r, invocará el constructor de movimiento si existe, de lo contrario invocará el constructor de copia.

La especificación de la expresión condicional no tiene permiso para un movimiento implícito de un argumento lvalue, incluso si la expresión condicional es parte de una expresión de retorno. Es posible que el lenguaje haya sido diseñado para permitir tal movimiento implícito, pero hasta donde yo sé, nunca fue propuesto. Además, la especificación existente de la expresión condicional ya es extremadamente complicada, lo que hace que este cambio en el lenguaje sea aún más difícil.

+0

No estoy seguro de que esto responda todas mis preguntas, aunque sí "corrige" el código específico con el que estoy jugando. ¿Por qué funciona tu forma de 2 declaraciones de devolución mientras que mi formulario no? ¿Es esto un problema de calidad del compilador? Supongo que parte de mi problema es que RVO depende completamente del compilador, mientras que el movimiento está bajo mi control. –

+0

@ IanM_Matrix1: no debe confiar en una optimización particular para la ganancia crítica de rendimiento, porque es una ciencia misteriosa y no puede predecir con fiabilidad cuándo se activará o no. Esto es lo que nos trae 'mover': la capacidad de controlar, de cierta manera, lo que está sucediendo. Si su clase es cara de copiar, pensaría en deshabilitar la copia para evitar el uso accidental. –

+0

Estoy totalmente de acuerdo con eso, pero a veces es necesario copiar ** y ** caro, aunque una función de tipo clon le daría un mejor control sobre eso. De todos modos, parece que mis preguntas han sido respondidas con la última actualización de Howard: 1 es respondida en la última actualización, 2 es respondida por 'do not' y 3 es respondida por su código original. Gracias Howard :) –