2012-11-17 21 views
16

Un C++Next blog post detto cheritorno con riferimento rvalue

A compute(…) 
{ 
    A v; 
    … 
    return v; 
} 

Se A ha un copia accessibile o spostare il costruttore, il compilatore può scegliere di elidere la copia. In caso contrario, se A dispone di un costruttore di movimento, v viene spostato. Altrimenti, se A ha un costruttore di copia, viene copiato v. In caso contrario, viene emesso un errore di compilazione.

Ho pensato di restituire sempre il valore senza std::move perché il compilatore sarebbe in grado di individuare la scelta migliore per gli utenti. Ma in un altro esempio tratto dal post sul blog

Matrix operator+(Matrix&& temp, Matrix&& y) 
    { temp += y; return std::move(temp); } 

Qui il std::move è necessario perché y devono essere trattati come valore assegnabile all'interno della funzione.

Ah, la mia testa quasi esplode dopo aver studiato questo post sul blog. Ho fatto del mio meglio per capire il ragionamento, ma più ho studiato, più mi sono confuso. Perché dovremmo restituire il valore con l'aiuto di std::move?

+1

Avete un link per l'articolo in questione? –

+1

[domande frequenti correlate] (http://stackoverflow.com/questions/3106110/) – fredoverflow

+1

http://cpp-next.com/archive/2009/09/making-your-next-move/, è una serie su rvalue reference – StereoMatching

risposta

21

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)); 
} 
+0

Vuoi dire che il compilatore può spostare o escludere implicitamente solo l'oggetto automatico? Quindi nel secondo caso dovremmo usare std :: move per aiutare il compilatore a capire che && v è un valore perché il nostro compilatore mister non è abbastanza intelligente da sapere che noi don hai più bisogno di v? – StereoMatching

+0

Bene, la verità è che un riferimento rvalore denominato è un'espressione lvalue. L'espressione 'v' è un lvalue, anche se il suo tipo è un riferimento di rvalue. Non è che il compilatore non sia abbastanza intelligente - sarebbe pericoloso per lui trattarlo come un valore. Aggiungerò un po 'alla mia risposta per spiegare perché. –

+0

Grazie, ho la sensazione di sapere che cosa è il riferimento di valore "ancora una volta".Questa cosa è piuttosto complicata, mi chiedo che questa funzione venga utilizzata solo da quegli sviluppatori di librerie e dai produttori di compilatori. – StereoMatching

5

Uno spostamento implicito dei risultati di una funzione è possibile solo per gli oggetti automatici. Un parametro di riferimento rvalore non denota un oggetto automatico, quindi è necessario richiedere lo spostamento esplicitamente in quel caso.

+0

Il punto chiave è che 'v' nel primo esempio viene distrutto comunque alla fine della funzione, quindi ha senso che l'ultima operazione su di esso sia una mossa piuttosto che una copia. Tuttavia nel secondo esempio, l'oggetto indicato da 'temp' vivrà oltre la fine della funzione. –

4

Il primo sfrutta NVRO, che è persino migliore dello spostamento. No la copia è migliore di quella economica.

Il secondo non può sfruttare NVRO. Supponendo che non ci sia elisione, return temp; chiamerebbe il costruttore di copie e return std::move(temp); chiamerebbe il costruttore di movimento. Ora, credo che entrambi abbiano lo stesso potenziale per essere elisi e quindi dovresti andare con quello più economico se non elidato, che sta usando std::move.

+2

Chiamare 'std :: move' inibisce NRVO, poiché l'espressione dell'istruzione return non è più" il nome di un oggetto automatico non volatile "(non che' temp' fosse un nome simile prima, ma comunque). – Xeo

Problemi correlati