2012-03-15 11 views
7

Ho un'API che assomiglia a questo:C++ 11: la semantica del movimento viene coinvolta per il passaggio per valore?

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

mi chiedo se sarebbe sensato consentire la semantica spostare in questo modo:

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 è solo boilerplate che il compilatore dovrebbe essere in grado di capire comunque?

+0

Il wstring ha un costruttore di movimento, quindi non è questo ciò che sta già accadendo? –

+0

Non puoi evitare il tmp e lo std :: move e passa wstring (target) nella chiamata di funzione direttamente? – juanchopanza

+0

Questa è forse una domanda stupida, ma perché passare un riferimento const normale normale non è un'opzione? Scrivere qualcosa (qui: una stringa) in un flusso non deve modificarlo, né richiedere una copia privata aggiuntiva. Quindi, davvero non vedo il ragionamento alla base di una copia temporale (possibilmente profonda) e quindi di usare semantica (distruttiva) sul temporaneo. Tranne che per usare la semantica del movimento, ovviamente. – Damon

risposta

15

"Passaggio per valore" può significare copiare o spostare; se l'argomento è un lvalue, viene invocato il costruttore della copia. Se l'argomento è un valore, viene invocato il costruttore di movimento. Non devi fare nulla di speciale che coinvolga i riferimenti di valore per ottenere la semantica del movimento con il passare per valore.

I scavare più a fondo questo argomento nel What are move semantics?

+0

Nel caso del passaggio per valore, e un argomento di rvalue, mi aspetterei che il temporaneo venga costruito esattamente dove era necessario, in modo che non ci fosse nulla da spostare. O per copiare, nei vecchi compilatori. –

+0

Forse; ottimizzazione del compilatore non è il mio territorio. – fredoverflow

+2

Non è proprio quello che chiamerei ottimizzazione (anche se ufficialmente lo è). L'elisione di un costruttore di copie può comportare un cambiamento nella semantica del programma, è legale perché lo standard fornisce al compilatore l'autorizzazione esplicita per farlo e, poiché può cambiare la semantica, deve essere preso in considerazione quando si valuta la correttezza del programma. –

2

Si dovrebbe preferire passaggio per valore se si sta andando a fare una copia con valori non mobili. Ad esempio, la tua versione const& di WriteDefaultFileOutput copia esplicitamente il parametro, quindi sposta la copia con la versione di spostamento. Il che significa che se WriteDefaultFileOutput viene chiamato con un valore mobile (xvalue o prvalue), verrà spostato e, se viene chiamato con un valore di l, verrà copiato.

Ciò significa che c'è no differenza tra le due forme di questa funzione. Considerate questo:

WriteDefaultFileOutput(L"SomeString"); 

Nel primo caso, si creerà una temporanea wstring. I temporanei sono valori preregolati, quindi verranno "spostati" nel parametro (poiché wstring ha un costruttore di mosse). Ovviamente, qualsiasi compilatore degno di questo nome eleverà la mossa e semplicemente costruirà il temporaneo direttamente nel parametro.

Nel secondo caso, più o meno accade la stessa cosa. Viene creato uno spazio temporaneo wstring. I temporanei sono prvalues, quindi possono essere associati ai tipi di parametri &&. Pertanto, chiamerà la prima versione della tua funzione con un riferimento al valore r al temporaneo. L'unica differenza possibile sarebbe da un compilatore che non elidere la mossa. E anche in questo caso, una mossa non è così costosa (a seconda dell'implementazione di basic_string).

Ora considerare questo:

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

Nel primo caso, la chiamata a WriteDefaultFileOutput causerà una copia del valore myStr nel parametro della funzione.

Nel secondo caso, myStr è un lvalue. È impossibile associare a un parametro &&. Pertanto, l'unica versione che può chiamare è la versione const&. Quella funzione costruisce manualmente una copia e quindi sposta la copia con l'altra.

Un effetto identico. La tua prima versione ha meno codice, quindi vai con quello per ovvi motivi.

In generale, direi che ci sono solo due motivi per prendere un parametro come &&:

  1. Si sta scrivendo un costruttore mossa.
  2. Si sta scrivendo una funzione di inoltro e occorre utilizzare l'inoltro perfetto.

In tutti gli altri casi in cui si desidera che il movimento sia possibile, basta prendere un valore. Se l'utente vuole copiare, lascialo copiare. Suppongo che se si vuole proibire esplicitamente la copia del parametro, si può prendere un &&. Ma il problema principale è di chiarezza.

Se si assume un parametro di valore e l'utente fornisce un valore mobile, il valore fornito dall'utente verrà sempre spostato. Ad esempio, la tua versione && di WriteDefaultFileOutput non ha per spostare effettivamente i dati dal suo parametro. Certamente. Ma non è necessario. Se avesse preso un valore, avrebbe già rivendicato i dati.

Pertanto, se una funzione accetta un parametro di valore e viene visualizzato un valore std::move in tale valore, allora è noto allo che l'oggetto che è stato spostato è vuoto. È garantito per essere stato spostato da.

Problemi correlati