2012-08-26 8 views
10

Si compila este programa con un compilador C++ 11, el vector no se mueve fuera de la función.Resultado del operador ternario no es un valor

#include <vector> 
using namespace std; 
vector<int> create(bool cond) { 
    vector<int> a(1); 
    vector<int> b(2); 

    return cond ? a : b; 
} 
int main() { 
    vector<int> v = create(true); 
    return 0; 
} 

Si devuelve la instancia de esta manera, se mueve.

if(cond) return a; 
else return b; 

Aquí hay un demo on ideone.

Lo probé con gcc 4.7.0 y MSVC10. Ambos se comportan de la misma manera.
Supongo por qué sucede esto:
El tipo de operadores ternarios es un valor l porque se evalúa antes de que se ejecute la declaración de retorno. En este punto, a y b aún no son valores x (que expirarán pronto).
¿Es correcta esta explicación?

¿Es esto un defecto en el estándar?
Esto claramente no es el comportamiento previsto y un caso muy común en mi opinión.

+0

"Si el segundo y tercer operandos son glvalues ​​de la misma categoría de valor y tienen el mismo tipo, el resultado es de ese tipo y categoría de valor [...]" §5.16/4 – Mat

+0

Ninguno de los dos ejemplos implica valores r o valores x. Pero es una pregunta interesante por qué se hace una copia en lugar de un movimiento. – aschepler

Respuesta

8

Estas son las citas relevantes estándar:

12.8 párrafo 32:

Copia elisión se permite en las siguientes circunstancias [...]

  • en un comunicado 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 la s AME cv-incondicional tipo que el tipo de retorno de la función, la operación de copia/movimiento puede omitirse mediante la construcción de la automática de objetos directamente en el valor de retorno de la función
  • [cuando throw ing, con condiciones]
  • [cuando la fuente es una temporal, con las condiciones]
  • [cuando catch ing por valor, con condiciones]

párrafo 33:

Cuando se cumplen o se cumplirían los criterios para 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 se designa mediante una resolución de sobrecarga lvalue para seleccionar el constructor de la copia primero se realiza 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.[Nota: Esta resolución de sobrecarga de dos etapas debe realizarse independientemente de si se producirá elisión de copia. Determina el nombre del constructor que se invocará si no se realiza una elisión, y el constructor seleccionado debe estar accesible incluso si se elimina la llamada. - finales nota]

Dado que la expresión en return (cond ? a : b); no es un simple nombre de la variable, no es elegible para la elisión copia o tratamiento rvalue. Tal vez un poco desafortunado, pero es fácil imaginar extender el ejemplo un poco más a la vez hasta crear un dolor de cabeza de una expectativa para las implementaciones del compilador.

Por supuesto puede evitar todo esto diciendo explícitamente std::move el valor de retorno cuando sabe que es seguro.

+0

Entonces, ¿está diciendo que el párrafo 33 es el único en el estándar por el cual un lvalue puede moverse a un valor de retorno y por lo tanto el hecho de que el párrafo 33 no se aplica a esta declaración de devolución? para elisión y no es un parámetro de función), significa que no se puede mover? No es que no crea esto, solo que no estoy seguro de haber seguido el razonamiento. –

+0

@SteveJessop: Eso suena bien. Sin 12.8p32-33, el único comportamiento correcto sería copiar al valor de retorno. Con p32 y sin p33, la implementación puede elegir entre copiar o elidir la copia. Con p32 y p33 (realidad), la implementación puede elegir entre mover o eludir el movimiento. La implementación nunca puede elegir entre copiar y mover: la resolución de sobrecarga y esta excepción particular deletrean lo que sucede si no se elimina. – aschepler

7

Esto lo arreglará

return cond ? std::move(a) : std::move(b); 

Considere el operador ternario en función, al igual que su código es

return ternary(cond, a, b); 

Los parámetros no se moverán de forma implícita, es necesario hacerlo explícito.

+1

'return std :: move (cond? A: b); 'también funciona. Es eso equivalente? –

+0

@ AaronMcDaid: Sí. – GManNickG

+0

esta es la respuesta correcta. – Walter

Cuestiones relacionadas