2010-04-05 15 views
10

Primo: dove sono definiti std::move e std::forward? So cosa fanno, ma non riesco a trovare la prova che sia necessaria un'intestazione standard per includerli. In gcc44 talvolta è disponibile std::move, e talvolta non lo è, quindi sarebbe utile una direttiva di inclusione definitiva.Alcuni chiarimenti sui riferimenti di rvalore

Quando si implementa la semantica del movimento, la sorgente viene presumibilmente lasciata in uno stato indefinito. Questo stato dovrebbe necessariamente essere uno stato valido per l'oggetto? Ovviamente, devi essere in grado di chiamare il distruttore dell'oggetto ed essere in grado di assegnarlo con qualunque mezzo la classe esponga. Ma dovrebbero essere valide altre operazioni? Suppongo che quello che sto chiedendo sia, se la tua classe garantisce certe invarianti, dovresti sforzarti di far rispettare quegli invarianti quando l'utente ha detto che non gli importa più di loro?

Avanti: quando non ti interessa la semantica del movimento, ci sono dei limiti che farebbero preferire un riferimento non const su un riferimento di rvalue quando si gestiscono i parametri di funzione? void function(T&); su void function(T&&); Dal punto di vista del chiamante, essere in grado di passare le funzioni valori temporanei è talvolta utile, quindi sembra che si debba concedere tale opzione ogni volta che è fattibile. E i riferimenti di valore sono essi stessi lvalue, quindi non puoi inavvertitamente chiamare un costruttore di mosse invece di un costruttore di copie, o qualcosa del genere. Non vedo un rovescio della medaglia, ma sono sicuro che ce n'è uno.

Che mi porta alla mia ultima domanda. Non è ancora possibile associare i provvisori a riferimenti non const. Ma puoi vincolarli a riferimenti non costanti. E quindi puoi passare quel riferimento come riferimento non const in un'altra funzione.

void function1(int& r) { r++; } 
void function2(int&& r) { function1(r); } 
int main() { 
    function1(5); //bad 
    function2(5); //good 
} 

Oltre al fatto che non fa nulla, c'è qualcosa di sbagliato in quel codice? Ovviamente il mio istinto non lo dice, dal momento che cambiare i riferimenti di valore è un po 'il punto centrale della loro esistenza. E se il valore passato è legittimamente const, il compilatore lo prenderà e ti urlerà contro. Ma da tutte le apparenze, questa è una soluzione di un meccanismo che presumibilmente è stato messo in atto per una ragione, quindi vorrei solo la conferma che non sto facendo nulla di sciocco.

risposta

10

Primo: dove sono definiti std :: move e std :: forward?

Vedere 20.3 Componenti di utilità, <utility>.


Nell'attuare semantica movimento, la sorgente è presumibilmente lasciato in uno stato indefinito. Questo stato dovrebbe necessariamente essere uno stato valido per l'oggetto?

Ovviamente, l'oggetto dovrebbe ancora essere distruttivo. Ma oltre a ciò, penso che sia una buona idea essere ancora assegnabile. Lo standard dice per oggetti che soddisfano "MoveConstructible" e "MoveAssignable":

[Nota: rv rimane un oggetto valido. Il suo stato non è specificato. - end note]

Ciò significherebbe, penso, che l'oggetto possa ancora partecipare a qualsiasi operazione che non precisi alcuna condizione. Questo include CopyConstructible, CopyAssignable, Destructible e altre cose. Si noti che questo non richiederà nulla per i propri oggetti dal punto di vista del linguaggio di base. I requisiti avvengono solo dopo aver toccato i componenti della libreria standard che indicano tali requisiti.


successivo: quando non si cura di semantica move, ci sono delle limitazioni che potrebbero causare un riferimento non const a essere preferito un riferimento rvalue quando si tratta di parametri di funzione?

Questo, purtroppo, fondamentalmente dipende dal fatto che il parametro è in un modello di funzione e utilizza un parametro di modello:

void f(int const&); // takes all lvalues and const rvalues 
void f(int&&); // can only accept nonconst rvalues 

Tuttavia, per un modello di funzione

template<typename T> void f(T const&); 
template<typename T> void f(T&&); 

Non è possibile Detto ciò, poiché il secondo template, dopo essere stato chiamato con un lvalue, avrà come parametro della dichiarazione sintetizzata il tipo U& per gli lvalues ​​non coerenti (e sarà una corrispondenza migliore), e U const& per const lvalues ​​(ed essere ambigui). A mia conoscenza, non esiste una regola di ordinamento parziale per disambiguare quella seconda ambiguità. Tuttavia, questo is already known.

-- Modifica --

Nonostante quel rapporto problema, non credo che i due modelli sono ambigue. L'ordinamento parziale renderà il primo modello più specializzato, perché dopo aver tolto i modificatori di riferimento e lo const, troveremo che entrambi i tipi sono gli stessi, e quindi notiamo che il primo modello aveva un riferimento a const. Lo standard dice (14.9.2.4)

Se, per un dato tipo, la deduzione ha esito positivo in entrambe le direzioni (ad es., i tipi sono identici dopo le trasformazioni precedenti) e se il tipo dal modello dell'argomento è più cv-qualificato del tipo dal modello di parametro (come descritto sopra) quel tipo è considerato più specializzato dell'altro.

Se per ogni tipo viene considerato un determinato modello è almeno altrettanto specializzato per tutti i tipi e più specializzato per alcuni tipi di tipi e l'altro modello non è più specializzato per alcun tipo o non è almeno altrettanto specializzato per qualsiasi tipo , quindi il modello dato è più specializzato dell'altro modello.

Questo rende il modello T const& il vincitore di ordinamento parziale (e GCC è effettivamente corretta per sceglierlo).

-- Modifica Fine --


che mi porta alla mia ultima domanda. Non è ancora possibile associare i provvisori a riferimenti non const. Ma puoi vincolarli a riferimenti non costanti.

Questo è ben spiegato in this article. La seconda chiamata che utilizza function2 accetta solo valori non validi. Il resto del programma non si accorgerà se sono modificati, perché non saranno più in grado di accedere a quei rvalues ​​in seguito! E il passaggio 5 non è un tipo di classe, quindi viene creato un elemento temporaneo nascosto e quindi passato al riferimento di valore int&&. Il codice che chiama function2 non sarà in grado di accedere a quell'oggetto nascosto qui, quindi non noterà alcuna modifica.

Una situazione diversa è se si fa questo:

SomeComplexObject o; 
function2(move(o)); 

Si hanno esplicitamente richiesto che o viene spostato, quindi sarà modificata secondo le sue specifiche mossa. Tuttavia lo spostamento è un'operazione logicamente non modificabile (vedere l'articolo). Questo significa che se si sposta o non non dovrebbe essere osservabile dal codice chiamante:

SomeComplexObject o; 
moveit(o); // #1 
o = foo; 

Se si cancella la linea che si muove, il comportamento sarà ancora lo stesso, perché è sovrascritto in ogni caso. Ciò significa tuttavia che il codice che utilizza il valore di o dopo essere stato spostato da è non valido, perché interrompe questo contratto implicito tra moveit e il codice chiamante. Pertanto, lo standard non specifica il valore concreto di un oggetto spostato dal contenitore.

+1

Grazie per l'elaborata risposta. Penso di capire le cose meno di prima, ma non per colpa tua. Secondo questo articolo, una funzione come 'void f (int &&);' non può accettare lvalues ​​a meno che non siano espressi espressi? Questo non è sicuramente il comportamento che ho visto, quindi presumo che questa sia una lacuna in gcc-4.4 L'implementazione, che rende tutti i test che ho fatto per capire come tutto interagisca praticamente senza valore: -/ –

+0

@Dennis, yep che è stato modificato nella bozza di lavoro qualche tempo fa: 'int &&' non si associa implicitamente a è troppo pericoloso –

+0

Il link "questo articolo" è morto. Puoi cambiarlo in uno attuale? –

2

incluso da utility


Here è l'articolo che ho letto su rvalues.

Non posso aiutarti con il riposo, mi dispiace.

4

dove sono std :: move e std :: forward definito?

std::move e std::forward sono dichiarati in <utility>. Vedi la sinossi all'inizio della sezione 20.3 [utilità].

Quando si implementa la semantica del movimento, la sorgente viene presumibilmente lasciata in uno stato indefinito.

Ovviamente dipende da come si implementa l'operatore di spostamento-costruttore e di assegnazione del movimento. Se si desidera utilizzare gli oggetti in contenitori standard, tuttavia, è necessario seguire i concetti MoveConstructible e MoveAssignable, che indicano che l'oggetto rimane valido, ma viene lasciato nello stato non specificato, vale a dire che è possibile distruggerlo.

Problemi correlati