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::move
funzione (esegue semplicemente la conversione). Il codice seguente invoca il costruttore di mosse per f1
e 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 foo
il costruttore prende un int
. Bene, potremmo invece usare A1 const&
, ma cosa foo
succede 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::forward
funzione in grado di inoltrare correttamente i riferimenti lvalue / rvalue. Per ulteriori informazioni su come std::forward
funziona, 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 T
costruttore. Ciò significa che se factory viene chiamato con un valore, T
il costruttore viene chiamato con un valore. Se factory viene chiamato con un lvalue, T
il 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 T
trova 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::move
chiamata è necessaria in:foo&& r = foo(); foo f = std::move(r);