2010-04-05 11 views
10

Primero: ¿dónde están definidos std::move y std::forward? Sé lo que hacen, pero no puedo encontrar pruebas de que se requiera un encabezado estándar para incluirlos. En gcc44 a veces std::move está disponible, y algunas veces no, por lo que una directiva de inclusión definitiva sería útil.Algunas aclaraciones sobre las referencias rvalue

Al implementar la semántica de movimiento, la fuente se deja presumiblemente en un estado indefinido. ¿Debería este estado necesariamente ser un estado válido para el objeto? Obviamente, debe poder invocar el destructor del objeto y asignarlo por cualquier medio que exponga la clase. Pero, si otras operaciones fueran válidas? Supongo que lo que estoy preguntando es, si su clase garantiza ciertas invariantes, ¿debería esforzarse por imponer esas invariantes cuando el usuario ha dicho que ya no se preocupan por ellas?

Siguiente: cuando no le importa la semántica de movimiento, ¿hay alguna limitación que haga que una referencia no constante sea preferible a una referencia rvalue cuando se trata de parámetros de función? void function(T&); sobre void function(T&&); Desde la perspectiva de una persona que llama, ser capaz de pasar funciones con valores temporales ocasionalmente es útil, por lo que parece que se debe otorgar esa opción siempre que sea factible hacerlo. Y las referencias rvalue son en sí mismas lvalues, por lo que no se puede inadvertidamente llamar a un constructor de movimiento en lugar de un constructor de copia, o algo así. No veo un inconveniente, pero estoy seguro de que hay uno.

Lo que me lleva a mi última pregunta. Aún no puede vincular temporalmente a referencias no const. Pero puede vincularlos a referencias de valores no const. Y luego puede pasar esa referencia como referencia no constante en otra función.

void function1(int& r) { r++; } 
void function2(int&& r) { function1(r); } 
int main() { 
    function1(5); //bad 
    function2(5); //good 
} 

Además de que no hace nada, ¿hay algún problema con ese código? Mi instinto dice, por supuesto, que no, ya que cambiar las referencias de valores es una especie de punto de su existencia. Y si el valor pasado es legítimamente const, el compilador lo atrapará y le gritará. Pero, por lo que parece, este es un rodeo de un mecanismo que presumiblemente se puso en marcha por una razón, así que solo me gustaría la confirmación de que no estoy haciendo nada tonto.

Respuesta

10

En primer lugar: ¿dónde están std :: movimiento y std :: adelante definidos?

Ver 20.3 componentes de servicios públicos, <utility>.


Al aplicar la semántica movimiento, la fuente es de suponer que quedan en un estado indefinido. ¿Debería este estado necesariamente ser un estado válido para el objeto?

Obviamente, el objeto debería ser destructible. Pero más allá de eso, creo que es una buena idea seguir siendo asignable. El estándar dice para los objetos que satisfacen "MoveConstructible" y "MoveAssignable":

[Nota: rv sigue siendo un objeto válido. Su estado no está especificado. - nota final]

Esto significa, creo, que el objeto aún puede participar en cualquier operación que no establezca ninguna condición previa. Esto incluye CopyConstructible, CopyAssignable, Destructible y otras cosas. Tenga en cuenta que esto no requerirá nada para sus propios objetos desde la perspectiva del lenguaje central. Los requisitos solo tienen lugar una vez que toca los componentes de la biblioteca estándar que establecen estos requisitos.


siguiente: cuando no se preocupan por la semántica movimiento, ¿existen limitaciones que causarían una referencia no const a ser preferible a una referencia de valor de lado derecho cuando se trata de parámetros de la función?

Esto, por desgracia, lo más importante depende de si el parámetro se encuentra en una plantilla de función y utiliza un parámetro de plantilla:

void f(int const&); // takes all lvalues and const rvalues 
void f(int&&); // can only accept nonconst rvalues 

Sin embargo, para una plantilla de función

template<typename T> void f(T const&); 
template<typename T> void f(T&&); 

No se puede dígalo, porque la segunda plantilla, después de ser llamada con un lvalue, tendrá como parámetro de la declaración sintetizada el tipo U& para los valores l de no contraste (y será una mejor coincidencia), y U const& para const lvalues ​​(y ser ambiguo). Que yo sepa, no existe una regla de ordenamiento parcial para eliminar la ambigüedad de esa segunda ambigüedad. Sin embargo, esto is already known.

-- Edición --

A pesar de que el informe tema, no creo que las dos plantillas son ambiguas. El pedido parcial hará que la primera plantilla sea más especializada, porque después de eliminar los modificadores de referencia y el const, encontraremos que ambos tipos son iguales, y luego notaremos que la primera plantilla tenía una referencia a const. El estándar dice (14.9.2.4)

Si, para un tipo dado, la deducción tiene éxito en ambas direcciones (es decir,, los tipos son idénticos después de las transformaciones anteriores) y si el tipo de la plantilla de argumento es más cv-cualificado que el tipo de la plantilla de parámetro (como se describió anteriormente), ese tipo se considera más especializado que el otro.

Si para cada tipo se considera que una plantilla determinada es al menos tan especializada para todos los tipos y más especializada para algunos tipos y la otra plantilla no es más especializada para ningún tipo o no es tan especializada para ningún tipo , entonces la plantilla dada es más especializada que la otra plantilla.

Esto hace que la plantilla T const& sea la ganadora del pedido parcial (y GCC es correcto en su caso).

-- Edit End --


que me lleva a mi última pregunta. Aún no puede vincular temporalmente a referencias no const. Pero puede vincularlos a referencias de valores no const.

Esto se explica muy bien en this article. La segunda llamada que usa function2 solo toma valores r no const. El resto del programa no se dará cuenta si se modifican porque ya no podrán acceder a esos valores. Y el 5 que pasa no es un tipo de clase, por lo que se crea un elemento temporal oculto y luego se pasa a la referencia de valor int&&. El código que llama al function2 no podrá acceder a ese objeto oculto aquí, por lo que no notará ningún cambio.

Una situación diferente es si lo hace éste:

SomeComplexObject o; 
function2(move(o)); 

que ha requerido explícitamente que o se mueve, por lo que será modificado de acuerdo con su especificación movimiento. Sin embargo, en movimiento es una operación que no modifica la lógica (ver el artículo). Esto significa que usted se mueva o no, no debería ser observable desde el código de llamada:

SomeComplexObject o; 
moveit(o); // #1 
o = foo; 

Si borra la línea que se mueve, el comportamiento todavía habrá la misma, porque se sobrescribe todos modos. Sin embargo, esto significa que el código que usa el valor de o después de haberlo movido es malo, porque rompe este contrato implícito entre moveit y el código de llamada. Por lo tanto, el Estándar no especifica el valor concreto de un contenedor movido.

+1

Gracias por la respuesta elaborada. Creo que entiendo las cosas menos que antes, pero no por tu culpa. Según ese artículo, una función como 'void f (int &&);' no puede aceptar lvalues ​​a menos que se emitan explícitamente. Definitivamente ese no es el comportamiento que he estado viendo, así que supongo que esto es una deficiencia en gcc-4.4 's. Implementación. Lo que hace que todas las pruebas que he estado haciendo entiendan cómo todo interactúa bastante inútil: -/ –

+0

@Dennis, sí, eso fue cambiado en el borrador de trabajo hace un tiempo: 'int &&' no se une implícitamente a lvalues ​​más. Es demasiado peligroso. –

+0

El enlace 'este artículo' está muerto. ¿Puedes cambiarlo por uno actual? –

2

incluido por utility


Here es el artículo que leí sobre rvalues.

No puedo ayudarte con el resto, lo siento.

4

dónde están std :: movimiento y std :: adelante definido?

std::move y std::forward se declaran en <utility>. Vea la sinopsis al comienzo de la sección 20.3 [utilidad].

Al aplicar la semántica movimiento, la fuente es de suponer que quedan en un estado indefinido.

Por supuesto, depende de cómo implemente el operador de movimiento y el de asignación de movimiento. Sin embargo, si desea utilizar sus objetos en contenedores estándar, debe seguir los conceptos MoveConstructible y MoveAssignable, que dicen que el objeto sigue siendo válido, pero se deja en estado no especificado, es decir, definitivamente puede destruirlo.

Cuestiones relacionadas