2012-03-15 13 views
7

Tengo una API que se parece a esto:C++ 11: ¿Se involucra la semántica de movimiento para pasar de valor?

void WriteDefaultFileOutput(std::wostream &str, std::wstring target) 
{ 
    //Some code that modifies target before printing it and such... 
} 

Me pregunto si sería sensato para permitir la semántica peligrosa de hacer esto:

void WriteDefaultFileOutput(std::wostream &str, std::wstring&& target) 
{ 
    //As above 
} 
void WriteDefaultFileOutput(std::wostream &str, std::wstring const& target) 
{ 
    std::wstring tmp(target); 
    WriteDefaultFileOutput(str, std::move(tmp)); 
} 

o se trata simplemente repetitivo que el compilador debería poder descifrar de todos modos?

+0

El wstring tiene un constructor de movimiento, ¿no es esto lo que ya está sucediendo? –

+0

¿No se puede evitar el tmp y el std :: mover y pasar wstring (destino) directamente en la llamada a la función? – juanchopanza

+0

Esta es quizás una pregunta estúpida, pero ¿por qué no es una opción pasar una referencia de referencia normal normal? Escribir algo (aquí: una cadena) en una transmisión no debería modificarlo ni requerir una copia privada adicional. Entonces, realmente no veo el razonamiento detrás de hacer una copia temporal (posiblemente profunda) y luego usar semánticas de movimiento (destructivas) en el temporal. Excepto por el uso de la semántica de movimientos, por supuesto. – Damon

Respuesta

15

"Pasar por valor" puede significar copiar o mover; si el argumento es un lvalue, se invoca el constructor de copia. Si el argumento es un valor r, se invoca el constructor de movimiento. No tiene que hacer nada especial que implique referencias de valores para obtener la semántica de movimiento con pase por valor.

que profundizar en este tema en What are move semantics?

+0

En el caso de pasar por valor, y un argumento de valor razonable, esperaría que el temporal se construyera exactamente donde se necesitaba, para que no hubiera nada que mover. O para copiar, en compiladores más antiguos. –

+0

Quizás; La tierra de optimización del compilador no es mi territorio. – fredoverflow

+2

No es lo que llamaría optimización (aunque oficialmente lo es). El uso de un constructor de copia puede generar un cambio en la semántica del programa, solo es legal porque el estándar le da al compilador autorización explícita para hacerlo, y debido a que puede cambiar la semántica, debe tenerse en cuenta al evaluar la corrección del programa. –

2

Usted debe preferir pase por valor si va a hacer una copia con los valores no móviles. Por ejemplo, su versión const& de WriteDefaultFileOutput copia explícitamente el parámetro, luego mueve la copia con la versión mover. Lo que significa que si se llama a WriteDefaultFileOutput con un valor móvil (valor xo prvalue), se moverá y si se llama con un valor l, se copiará.

Esto significa que hay no diferencia entre las dos formas de esta función. Considere esto:

WriteDefaultFileOutput(L"SomeString"); 

En el primer caso, se creará un temporal wstring. Las temporarias son valores prvariedades, por lo que se "moverán" al parámetro (dado que wstring tiene un constructor de movimiento). Por supuesto, cualquier compilador que se precie saldrá del movimiento y simplemente construirá el temporal directamente en el parámetro.

En su segundo caso, sucede más o menos lo mismo. Se crea un wstring temporal. Los temporarios son valores prvaluados, por lo que pueden enlazarse a los tipos de parámetros &&. Por lo tanto, llamará a la primera versión de su función con una referencia de valor r al temporal. La única diferencia posible sería un compilador que no elide el movimiento. E incluso entonces, una mudanza no es tan costosa (dependiendo de su implementación basic_string).

ahora esto:

std::wstring myStr{L"SomeString"}; 
WriteDefaultFileOutput(myStr); 

En el primer caso, la llamada a WriteDefaultFileOutput causará una copia del valor myStr en el parámetro de la función.

En el segundo caso, myStr es un lvalue. Es no se puede se unen a un parámetro &&. Por lo tanto, la única versión que puede llamar es la versión const&. Esa función construirá manualmente una copia, y luego moverá la copia con la otra.

Un efecto idéntico. Su primera versión tiene menos código, así que vaya con eso por razones obvias.

En general, yo diría que sólo hay dos razones para tomar un parámetro como &&:

  1. Estás escribiendo un constructor movimiento.
  2. Está escribiendo una función de reenvío y necesita un reenvío perfecto.

En todos los demás casos en los que desee que el movimiento sea posible, simplemente tome un valor. Si el usuario quiere copiar, déjelos copiar. Supongo que si quieres prohibir explícitamente la copia del parámetro, puedes tomar un &&. Pero el problema principal es uno de claridad.

Si toma un parámetro de valor y el usuario proporciona un valor móvil, entonces el valor proporcionado por el usuario será siempre se moverá. Por ejemplo, su versión && de WriteDefaultFileOutput no tiene tiene para mover realmente los datos de su parámetro. Ciertamente puede. Pero no tiene por qué. Si tomó un valor, entonces ya habría reclamado los datos.

Por lo tanto, si una función toma un parámetro de valor, y se ve un std::move en ese valor, entonces usted sabe que el objeto que se movía está vacía. Se ha movido .

Cuestiones relacionadas