2011-09-22 14 views
66

Mi sono appena trovato a non comprendere appieno la logica di std::move().In che modo std :: move() trasferisce i valori in RValues?

All'inizio, ho cercato su Google ma sembra che ci siano solo documenti su come utilizzare std::move(), non su come funziona la sua struttura.

Voglio dire, so qual è la funzione membro del modello, ma quando guardo nella definizione std::move() in VS2010, è ancora confusa.

la definizione di std :: move() è riportata di seguito.

template<class _Ty> inline 
typename tr1::_Remove_reference<_Ty>::_Type&& 
    move(_Ty&& _Arg) 
    { // forward _Arg as movable 
     return ((typename tr1::_Remove_reference<_Ty>::_Type&&)_Arg); 
    } 

Qual è strano prima per me è il parametro, (_Ty & & _Arg), perché quando io chiamo la funzione di come si vede qui sotto,

// main() 
Object obj1; 
Object obj2 = std::move(obj1); 

è uguale fondamentalmente per

// std::move() 
_Ty&& _Arg = Obj1; 

Ma come già sapete, non è possibile collegare direttamente un LValue a un riferimento RValue, il che mi fa pensare che dovrebbe essere così.

_Ty&& _Arg = (Object&&)obj1; 

Tuttavia, questo è assurdo perché std :: move() deve funzionare per tutti i valori.

Quindi, per capire a fondo come funziona, dovrei dare un'occhiata anche a queste strutture.

template<class _Ty> 
struct _Remove_reference 
{ // remove reference 
    typedef _Ty _Type; 
}; 

template<class _Ty> 
struct _Remove_reference<_Ty&> 
{ // remove reference 
    typedef _Ty _Type; 
}; 

template<class _Ty> 
struct _Remove_reference<_Ty&&> 
{ // remove rvalue reference 
    typedef _Ty _Type; 
}; 

Sfortunatamente è ancora così confuso e non capisco.

So che questo è tutto a causa della mia mancanza di competenze di sintassi di base su C++. Mi piacerebbe sapere come funzionano a fondo e tutti i documenti che posso ottenere su Internet saranno più che graditi. (Se puoi semplicemente spiegare questo, sarà fantastico).

+7

"So che questo è tutto a causa della mia mancanza di competenze di sintassi di base su C++." Allora non sei pronto per sapere come funziona. Tutto quello che devi sapere è come scrivere un costruttore di mosse, e quando e come usare 'std :: move' quando vuoi spostare costruire qualcosa. I dettagli di questo processo, la "magia profonda" che C++ 11 usava per far funzionare questo processo, non sono qualcosa di cui devi preoccuparti. Semplicemente non hai le basi della conoscenza per capire le particolari interazioni di funzionalità che fanno funzionare tutto. –

+0

Cosa disse Nicol Bolas. Se conosci te stesso che ti mancano le abilità di base, questo è il problema che devi risolvere prima, non la tua incapacità di capire std :: move! –

+11

@NicolBolas Al contrario, la domanda stessa mostra che l'OP si sta sottomettendo in quella dichiarazione. È proprio OP * afferrare * le abilità di sintassi di base che consente loro di porre anche la domanda. –

risposta

111

Si comincia con la funzione di trasferimento (che ho ripulito un po '):

template <typename T> 
typename remove_reference<T>::type&& move(T&& arg) 
{ 
    return static_cast<typename remove_reference<T>::type&&>(arg); 
} 

Cominciamo con la parte più facile - cioè, quando la funzione viene chiamata con rvalue:

Object a = std::move(Object()); 
// Object() is temporary, which is prvalue 

e il nostro modello di move viene istanziato come segue:

// move with [T = Object]: 
remove_reference<Object>::type&& move(Object&& arg) 
{ 
    return static_cast<remove_reference<Object>::type&&>(arg); 
} 

Dal remove_reference converte T& a T o T&&-T, e Object non è di riferimento, la nostra funzione finale è:

Object&& move(Object&& arg) 
{ 
    return static_cast<Object&&>(arg); 
} 

Ora, ci si potrebbe chiedere: abbiamo anche bisogno del cast? La risposta è: sì, lo facciamo. La ragione è semplice; il riferimento di riferimento è trattato come lvalue (e la conversione implicita da lvalue a riferimento di rvalue è vietata dallo standard).


Ecco cosa accade quando chiamiamo move con Ivalue:

Object a; // a is lvalue 
Object b = std::move(a); 

e corrispondente move istanziazione:

// move with [T = Object&] 
remove_reference<Object&>::type&& move(Object& && arg) 
{ 
    return static_cast<remove_reference<Object&>::type&&>(arg); 
} 

Nuovamente, remove_reference converte Object& a Object e otteniamo:

Object&& move(Object& && arg) 
{ 
    return static_cast<Object&&>(arg); 
} 

Ora arriviamo alla parte difficile: cosa significa Object& && e come può essere associato a lvalue?

Per consentire l'inoltro perfetta, C++ standard di 11 fornisce regole speciali per riferimento crollano, che sono i seguenti:

Object & & = Object & 
Object & && = Object & 
Object && & = Object & 
Object && && = Object && 

Come si può vedere, in queste regole Object& && significa in realtà Object&, che è lvalue pianura riferimento che consente lvalue vincolanti.

funzione finale è quindi:

Object&& move(Object& arg) 
{ 
    return static_cast<Object&&>(arg); 
} 

non dissimile l'istanza precedente con rvalue - entrambi espressi suo argomento riferimento rvalue e poi restituirlo. La differenza è che la prima istanziazione può essere utilizzata solo con i valori rvalue, mentre la seconda funziona con i valori lvalue.


Per spiegare perché abbiamo bisogno remove_reference un po 'di più, proviamo questa funzione

template <typename T> 
T&& wanna_be_move(T&& arg) 
{ 
    return static_cast<T&&>(arg); 
} 

e creare un'istanza con lvalue.

// wanna_be_move [with T = Object&] 
Object& && wanna_be_move(Object& && arg) 
{ 
    return static_cast<Object& &&>(arg); 
} 

Applicando il riferimento collasso regole di cui sopra, si può vedere otteniamo funzione che è inutilizzabile come move (per dirla semplicemente, si chiamano con lvalue, si ottiene lvalue indietro). Se mai, questa funzione è la funzione di identità.

Object& wanna_be_move(Object& arg) 
{ 
    return static_cast<Object&>(arg); 
} 
+2

Bella risposta. Anche se capisco che per un lvalue è una buona idea valutare 'T' come' Object & ', non sapevo che questo è veramente fatto. Mi sarei aspettato che anche 'T' valutasse' Object' in questo caso, poiché pensavo che questo fosse il motivo per l'introduzione dei riferimenti al wrapper e 'std :: ref', o non lo era. –

+0

È [questo articolo di Wikipedia] (http://en.wikipedia.org/wiki/C%2B%2B11#Wrapper_reference) obsoleto/disinformato o c'è una differenza nascosta che non ho realizzato? –

+0

Questa è una risposta davvero buona e chiara, +1. – Xeo

2

_Ty è un parametro di template, e in questa situazione

Object obj1; 
Object obj2 = std::move(obj1); 

_Ty è di tipo "Object &"

motivo per cui la _Remove_reference è necessario.

Sarebbe più come

typedef Object& ObjectRef; 
Object obj1; 
ObjectRef&& obj1_ref = obj1; 
Object&& obj2 = (Object&&)obj1_ref; 

Se non avessimo rimosso il riferimento sarebbe come stavamo facendo

Object&& obj2 = (ObjectRef&&)obj1_ref; 

Ma ObjectRef & & riduce a oggetto &, che noi couldn legare a obj2.

La ragione per cui riduce questo modo è di supportare l'inoltro perfetto. Vedi this paper.

+0

Questo non spiega tutto sul perché sia ​​necessario il comando "_Remove_reference_". Ad esempio, se hai 'Object &' come typedef e prendi un riferimento ad esso, ottieni ancora 'Object &'. Perché non funziona con &&? C'è una risposta a questo, e ha a che fare con l'inoltro perfetto. –

+2

Vero. E la risposta è molto interessante. Un & && è ridotto ad A &, quindi se provassimo ad usare (ObjectRef &&) obj1_ref, otterremmo Object e invece in questo caso. –

Problemi correlati