Dichiara un riferimento al valore (doc proposta standard).
Ecco un'introduzione ai riferimenti di valore .
Ecco un fantastico approfondimento sui riferimenti di valore di uno degli sviluppatori di librerie standard di Microsoft .
ATTENZIONE: l'articolo collegato su MSDN ("Riferimenti Rvalue: funzionalità C ++ 0x in VC10, parte 2") è un'introduzione molto chiara ai riferimenti Rvalue, ma fa delle dichiarazioni sui riferimenti Rvalue che un tempo erano vere nella bozza C ++ 11 standard, ma non sono veri per quello finale! In particolare, in vari punti si dice che i riferimenti ai valori possono legarsi ai valori, che una volta era vero, ma è stato modificato (ad es. Int x; int && rrx = x; non compila più in GCC)
La più grande differenza tra un riferimento C ++ 03 (ora chiamato riferimento lvalue in C ++ 11) è che può legarsi a un valore come un temporaneo senza dover essere const. Pertanto, questa sintassi è ora legale:
T&& r = T();
i riferimenti rvalue prevedono principalmente quanto segue:
Sposta semantica . Ora è possibile definire un costruttore di movimenti e un operatore di assegnazione di movimenti che accetta un riferimento di valore anziché il normale riferimento di valore costante. Una mossa funziona come una copia, tranne per il fatto che non è obbligata a mantenere invariata la fonte; infatti, di solito modifica l'origine in modo tale da non possedere più le risorse spostate. Questo è ottimo per eliminare copie estranee, specialmente nelle implementazioni di librerie standard.
Ad esempio, un costruttore di copie potrebbe apparire così:
foo(foo const& other)
{
this->length = other.length;
this->ptr = new int[other.length];
copy(other.ptr, other.ptr + other.length, this->ptr);
}
Se a questo costruttore fosse passato un temporaneo, la copia sarebbe superflua perché sappiamo che il temporaneo sarà semplicemente distrutto; perché non utilizzare le risorse temporanee già assegnate? In C ++ 03, non c'è modo di impedire la copia in quanto non possiamo determinare che ci è stato passato un temporaneo. In C ++ 11, possiamo sovraccaricare un costruttore di mosse:
foo(foo&& other)
{
this->length = other.length;
this->ptr = other.ptr;
other.length = 0;
other.ptr = nullptr;
}
Notate qui la grande differenza: il costruttore di mosse in realtà modifica il suo argomento. Ciò "sposterebbe" effettivamente il temporaneo nell'oggetto in costruzione, eliminando così la copia non necessaria.
Il costruttore di mosse verrebbe utilizzato per i provvisori e per i riferimenti a valori non cost che vengono esplicitamente convertiti in riferimenti a valore utilizzando la std::movefunzione (esegue semplicemente la conversione). Il codice seguente invoca il costruttore di mosse per f1e f2:
foo f1((foo())); // Move a temporary into f1; temporary becomes "empty"
foo f2 = std::move(f1); // Move f1 into f2; f1 is now "empty"
Inoltro perfetto . i riferimenti rvalue ci consentono di inoltrare correttamente argomenti per funzioni basate su modelli. Prendiamo ad esempio questa funzione di fabbrica:
template <typename T, typename A1>
std::unique_ptr<T> factory(A1& a1)
{
return std::unique_ptr<T>(new T(a1));
}
Se lo chiamassimo factory<foo>(5), l'argomento verrà dedotto int&, il che non si legherà a un letterale 5, anche se fooil costruttore prende un int. Bene, potremmo invece usare A1 const&, ma cosa foosuccede se prende l'argomento del costruttore come riferimento non const? Per rendere una funzione di fabbrica veramente generica, dovremmo sovraccaricare la fabbrica A1&e accenderla A1 const&. Ciò potrebbe andare bene se la fabbrica accetta 1 tipo di parametro, ma ogni tipo di parametro aggiuntivo moltiplicherebbe il sovraccarico necessario impostato per 2. Questo è molto rapidamente non mantenibile.
i riferimenti rvalue risolvono questo problema consentendo alla libreria standard di definire una std::forwardfunzione in grado di inoltrare correttamente i riferimenti lvalue / rvalue. Per ulteriori informazioni su come std::forwardfunziona, vedere questa risposta eccellente .
Questo ci consente di definire la funzione di fabbrica in questo modo:
template <typename T, typename A1>
std::unique_ptr<T> factory(A1&& a1)
{
return std::unique_ptr<T>(new T(std::forward<A1>(a1)));
}
Ora il valore rvalue / lvalue-ness dell'argomento viene conservato quando viene passato al Tcostruttore. Ciò significa che se factory viene chiamato con un valore, Til costruttore viene chiamato con un valore. Se factory viene chiamato con un lvalue, Til costruttore viene chiamato con un lvalue. La funzione di fabbrica migliorata funziona a causa di una regola speciale:
Quando il tipo di parametro della funzione è nel formato in T&&cui si Ttrova un parametro del modello e l'argomento della funzione è un valore di tipo A, il tipo A&viene utilizzato per la deduzione dell'argomento del modello.
Quindi, possiamo usare factory in questo modo:
auto p1 = factory<foo>(foo()); // calls foo(foo&&)
auto p2 = factory<foo>(*p1); // calls foo(foo const&)
Importanti proprietà di riferimento del valore :
- Per la risoluzione del sovraccarico, i lvalues preferiscono l'associazione ai riferimenti ai valori e i rvalues preferiscono l'associazione ai riferimenti rvalue . Ecco perché i provvisori preferiscono invocare un costruttore di spostamento / operatore di assegnazione di spostamento rispetto a un costruttore di copia / operatore di assegnazione.
- i riferimenti al valore si legheranno implicitamente ai valori e ai provvisori che sono il risultato di una conversione implicita . cioè
float f = 0f; int&& i = f;è ben formato perché float è implicitamente convertibile in int; il riferimento sarebbe a un temporaneo che è il risultato della conversione.
- I riferimenti ai valori nominali sono valori. I riferimenti ai valori senza nome sono valori. Questo è importante per capire perché la
std::movechiamata è necessaria in:foo&& r = foo(); foo f = std::move(r);