2012-03-19 17 views
20

En 11, los parámetros de valor de C++ (y otros valores) disfrutan de movimiento implícito cuando regresaron:¿Por qué C++ 11 tiene movimientos implícitos para los parámetros de valor, pero no para los parámetros de valor?

A func(A a) { 
    return a; // uses A::A(A&&) if it exists 
} 

Al menos en MSVC 2010, parámetros de referencia rvalue necesitan std::move:

A func(A && a) { 
    return a; // uses A::A(A const&) even if A::A(A&&) exists 
} 

me podría imaginar que las funciones internas, una referencia rvalue y un valor se comportan de manera similar, con la única diferencia de que en el caso de los valores, la función en sí misma es responsable de la destrucción, mientras que para las referencias rvalue, la responsabilidad está fuera.

¿Cuál es la motivación para tratarlos de manera diferente en el estándar?

Respuesta

24

El comité de normalización gastaron gran esfuerzo en la creación de redacción de manera que se mueve sólo ocurriría nunca en exactamente dos circunstancias:

  1. Cuando es claramente sea seguro hacerlo.
  2. Cuando el usuario explícitamente pregunta (a través de std::move o un modelo similar).

Un parámetro de valor se destruirá indudablemente al final de la función. Por lo tanto, devolverlo por movimiento es claramente seguro; no puede ser tocado por otro código después de la devolución (no a menos que intente deliberadamente romper las cosas, en cuyo caso probablemente desencadenó un comportamiento indefinido). Por lo tanto, se puede mover desde la devolución.

Una variable && podría estar refiriendo a un temporal. Pero podría estar refiriéndose a un lvalue (una variable nombrada). Por lo tanto, no es claramente seguro para moverse desde él; la variable original podría estar al acecho. Y como no hizo explícitamente solicite pasar de ella (es decir: no llamó al std::move en esta función), no se puede realizar ningún movimiento.

La única vez que una variable && se moverá de forma implícita (es decir: sin std::move) es cuando regrese ella. std::move<T> devuelve un T&&. Es legal que ese valor devuelto invoque el constructor de movimiento, porque es un valor de retorno.

Ahora es muy difícil llamar A func(A &&a) con un valor- sin llamar std::move (o un reparto equivalente). Por lo tanto, técnicamente, debería estar bien para los parámetros del tipo && del que se va a mover implícitamente. Pero el comité de estándares quería que los movimientos fueran explícitos para los tipos &&, solo para asegurarse de que el movimiento no ocurriera implícitamente dentro del alcance de esta función. Es decir, no puede utilizar el conocimiento fuera de función sobre el origen del &&.

En general, solo debe tomar los parámetros && en dos casos: o bien está escribiendo un constructor de movimiento (o un operador de asignación de movimiento, pero incluso eso puede hacerse por valor), o está escribiendo una función de reenvío . Puede haber algunos otros casos, pero no debe tomar && a menos que tenga algo especial en mente.Si A es un tipo movible, simplemente tómalo por valor.

+0

Si tiene una función 'A f();', si llama a 'a (f())' se llamaría 'a (A &&)' (ya que lo está llamando con un temporal)? Nunca dejé en claro las reglas sobre esto tampoco. –

+2

@Seth, en resumen, sí. Considera 'A f()', que devuelve una instancia de 'A' por valor. Al hacerlo, no garantiza que ni las funciones a las que llama puedan contener una referencia a esa instancia. Ahora, 'a (f())' pasa inmediatamente esa instancia a 'a()', sin dar al que llama la posibilidad de mantener una referencia a él mismo (las cosas serían muy diferentes si emitiera 'A my_a; a (my_a = f()); 'por ejemplo). Pero en su situación, es perfectamente seguro que 'a (A &&)' sea llamado, y realmente tiene prioridad sobre 'a (const A &)' IIRC. –

+0

Creo que tu respuesta es un poco ambigua. Esperaba una declaración clara de uno de los siguientes: (1) los parámetros '&&' * deben * moverse implícitamente, o (2) * no * se deben mover implícitamente, o bien (3) es una elección arbitraria para el compilador. Creo que, en este caso, el movimiento implícito es obligatorio y la versión de MSVC es incorrecta. 'return x;' debe ser reescrito implícitamente como 'return move (x);', donde 'x' es un parámetro, y' x' es 'A &&', (¡y 'A' no es un tipo de referencia en sí mismo!), y el tipo de devolución es 'A' (o incluso' A && '?). Creo que eso está garantizado. –

4

En el primer caso, el compilador sabe que a se va y nada será capaz de aferrarse a ella: claramente, este objeto se puede mover desde y si no es que serán destruidos. En el segundo caso, la referencia de valor real indica que es permisible moverse del objeto y que quien llama no espera que el objeto se quede. Sin embargo, es la elección de la función si aprovecha este permiso o no, y puede haber razones por las que la función a veces quiere pasar del argumento y, a veces, no quiere hacerlo. Si el compilador tuviera la libertad de moverse de este objeto, no habría manera de evitar que el compilador lo haga. Sin embargo, usando std::move(a) ya hay una manera de indicar que se desea mover desde el objeto.

La regla general en el estándar es que el compilador solo mueve objetos implícitamente que se sabe que desaparecen. Cuando aparece una referencia rvalue, el compilador no sabe realmente que el objeto está a punto de desaparecer: si fue explícitamente std::move() ed, realmente se mantiene.

Cuestiones relacionadas