Quindi, supponiamo di avere:
A compute()
{
A v;
…
return v;
}
E si sta facendo:
A a = compute();
Ci sono due trasferimenti (copiare o spostare) che sono coinvolti in questa espressione. Per prima cosa l'oggetto denotato da v
nella funzione deve essere trasferito al risultato della funzione, cioè il valore donato dall'espressione compute()
. Chiamiamolo trasferimento 1. Quindi, questo oggetto temporaneo viene trasferito per creare l'oggetto denotato da a
- trasferimento 2.
In molti casi, sia di trasferimento 1 e 2 possono essere elisa dal compilatore - dell'oggetto v
è costruito direttamente nella posizione di a
e non è necessario il trasferimento. In questo esempio, il compilatore deve utilizzare l'ottimizzazione del valore restituito con nome per il trasferimento 1, poiché l'oggetto che viene restituito è denominato. Se disabilitiamo copia/sposta elision, tuttavia, ogni trasferimento implica una chiamata al costruttore di copie di A o al suo costruttore di mosse. Nella maggior parte dei compilatori moderni, il compilatore vedrà che v
sta per essere distrutto e lo sposterà prima nel valore di ritorno. Quindi questo valore di ritorno temporaneo verrà spostato in a
. Se A
non ha un costruttore di movimento, verrà invece copiato per entrambi i trasferimenti.
Vediamo ora:
A compute(A&& v)
{
return v;
}
Il valore stiamo tornando deriva dal riferimento essendo passato alla funzione. Il compilatore non presume semplicemente che v
sia temporaneo e che sia corretto spostarsi da esso . In questo caso, il trasferimento 1 sarà una copia. Quindi il trasferimento 2 sarà una mossa - va bene perché il valore restituito è ancora temporaneo (non abbiamo restituito un riferimento). Ma dal momento che sappiamo che abbiamo preso un oggetto che si possa passare da, perché il nostro parametro è un riferimento rvalue, possiamo dire in modo esplicito al compilatore di trattare v
come temporanea con std::move
:
A compute(A&& v)
{
return std::move(v);
}
Ora sia Transfer 1 che Transfer 2 saranno mosse.
La ragione per cui il compilatore non tratta automaticamente v
, definito come A&&
, come rvalue è uno di sicurezza. Non è semplicemente troppo stupido per capirlo. Una volta che un oggetto ha un nome, può essere riferito a più volte nel codice. Considerare:
A compute(A&& a)
{
doSomething(a);
doSomethingElse(a);
}
Se a
è stato trattato automaticamente come rvalue, doSomething
sarebbe libero di strappare le sue budella, il che significa che il a
essere passato al doSomethingElse
potrebbe non essere valido. Anche se doSomething
ha preso il suo argomento per valore, l'oggetto sarebbe stato spostato da e quindi non valido nella riga successiva. Per evitare questo problema, i riferimenti rvalue nominati sono lvalue. Ciò significa che quando viene chiamato il numero doSomething
, nel caso peggiore verrà copiato lo a
, se non appena preso dal riferimento lvalue, sarà comunque valido nella riga successiva.
Spetta all'autore di compute
dire "okay, ora consento il trasferimento di questo valore, perché so per certo che si tratta di un oggetto temporaneo". Lo fai dicendo std::move(a)
. Ad esempio, si potrebbe dare doSomething
una copia e quindi consentire doSomethingElse
per spostarsi da esso:
A compute(A&& a)
{
doSomething(a);
doSomethingElse(std::move(a));
}
Avete un link per l'articolo in questione? –
[domande frequenti correlate] (http://stackoverflow.com/questions/3106110/) – fredoverflow
http://cpp-next.com/archive/2009/09/making-your-next-move/, è una serie su rvalue reference – StereoMatching