Move Semantics: Rvalue, std::move e Spostamenti
La copia richiede tempo a runtime quando si ha a che fare con grandi mole di dati, come allocazioni gigantesche (es. Gigabyte di memoria, file aperti su disco, socket di rete connessi). Spesso non ti serve duplicare questi oggetti, ma vuoi solo trasferirne la proprietà (ownership) da un punto A ad un punto B senza incorrere nei costi ciclopici dei Copy Constructors.
Invece di copiare… puoi Spostare (Move).
Se sposti un oggetto y in un oggetto x, tu svuoti (cannibalizzi) le risorse di y iniettandole a costo zero in x. y finisce in uno speciale stato chiamato “moved-from” (spostato da) e non lo puoi più utilizzare tranne che per distruggerlo o per sovrascriverlo.
Value Categories (lvalue vs rvalue)
Section titled “Value Categories (lvalue vs rvalue)”Il compilatore stabilisce da solo se usare un Copy o un Move basandosi sulla “categoria di valore” dell’espressione che abbiamo a destra dell’assegnazione. Ne vediamo in forma semplificata due su tutte:
- lvalue (left-value): un oggetto che possiede un nome. È un dato concreto.
- rvalue (right-value): un oggetto volatile o temporaneo (un letterale, un risultato di un’operazione che non viene assegnato a nulla, una funzione che ritorna per valore). Non ha un nome.
Se passi un lvalue a una funzione, il compilatore innesca di default la Copia (non oserebbe mai cannibalizzare un oggetto vivo che possiede un nome e potresti voler riusare).
Se passi un rvalue, innescherà lo Spostamento (tanto l’oggetto è temporaneo e sta per essere spazzato via comunque, tanto vale rubargli il buffer!).
La Funzione std::move
Section titled “La Funzione std::move”Puoi convertire (forzare tramite “cast”) un lvalue a presentarsi al compilatore come un rvalue reference usando la potente funzione std::move.
Attenzione:
std::movenon muove un bel niente, fa solo finta che il tuo lvalue sicuro e col nome sia invece un passeggero rvalue temporaneo che il sistema può saccheggiare.
Move Constructor e Move Assignment
Section titled “Move Constructor e Move Assignment”Esattamente come la controparte di copia, li implementiamo. A differenza loro, accettano una Rvalue Reference (denotata dal doppio ampersand &&), la quale non è costante (perché il nostro scopo è cannibalizzarne l’interno alterando l’originale).
Come visto, le due regole d’oro dello spostamento interno sono rubare il puntatore per sé, per poi invalidare il vecchio puntatore facendolo puntare a nullptr. Nel C++, il comando delete nullptr; non fa nulla ed è totalmente sicuro, ergo non vi saranno doppi rilasci di memoria distruttivi.