2014-12-03 13 views
7
#include <iostream> 
#include <string> 
#include <map> 

struct A { 
    int n { 42 }; 
    std::string s { "ciao" }; 
}; 

int main() { 
    A a; 
    std::map<std::string, A> m; 
    std::cout << "a.s: " << a.s << std::endl; // print: "a.s: ciao" 
    m.emplace(a.s, std::move(a)); // a.s is a member of a, moved in the same line 
    std::cout << "in map: " << m.count("ciao") << std::endl; // print: "in map: 1" 
    std::cout << "a.s: " << a.s << std::endl; // print: "a.s: " (as expected, it has been moved) 
} 

È sicuro passare come argomento un membro di un oggetto "in movimento"? In questo caso, emplace sembra funzionare: la mappa ha la chiave prevista.È sicuro passare come argomento un membro di un oggetto che si sta muovendo

+1

@JoachimPileborg In questo caso, nessun oggetto 'A' si crea nella chiamata, in quanto' std :: mappa :: emplace' prende riferimenti universali. Se il secondo argomento fosse preso in base al valore, allora l'ordine di valutazione sarebbe importante. –

+1

@DDrmmr Dopo aver letto alcuni riferimenti/specifiche/risposta Wintermutes, hai ragione. Questo dovrebbe essere sicuro. –

risposta

3

Interessante. Penso che sia sicuro, per ragioni contorte. (Per la cronaca, ho anche lo considerano molto cattivo stile - una copia esplicito ti costa niente qui, dal momento che verrà spostato nella mappa.)

Prima di tutto, la chiamata di funzione reale non è un problema . std::move esegue il cast di a solo su un riferimento di rvalue e i riferimenti di rvalue sono solo riferimenti; a non viene immediatamente spostato. emplace_back inoltra i suoi parametri a un costruttore di std::pair<std::string, A>, ed è qui che le cose diventano interessanti.

Quindi, quale costruttore di std::pair viene utilizzato? Ha piuttosto molti, ma due sono rilevanti:

pair(const T1& x, const T2& y); 
template<class U, class V> pair(U&& x, U&&y); 

(Vedi 20.3.2 nello standard), dove T1 e T2 sono gli argomenti di template di std::pair. Come per 13.3, finiamo in quest'ultimo con U == const T1& e V == T2, il che ha un senso intuitivo (altrimenti muoversi in uno std::pair sarebbe effettivamente impossibile). Questo ci lascia con un costruttore del modulo

pair(const T1& x, T2 &&y) : first(std::forward(x)), second(std::forward(y)) { } 

come da 20.3.2 (6-8).

Quindi, è sicuro? In linea di massima, std::pair è definito in dettaglio, incluso il layout della memoria. In particolare, si afferma che

T1 first; 
T2 second; 

sono disponibili in questo ordine, in modo first verranno inizializzate prima second. Ciò significa che nel tuo caso particolare, la stringa verrà copiata prima che venga spostata e tu sei al sicuro.

Tuttavia, se si stavano facendo il contrario:

m.emplace(std::move(A.s), A); // huh? 

... poi si otterrebbe effetti divertenti.

+0

Dove nello standard è specificato come deve essere implementato 'std :: map :: emplace'? Penso che tu stia saltando alle conclusioni qui. –

+0

23.2.4, nella tabella 102 (per tutti i contenitori associativi). – Wintermute

+0

Sei sicuro, il compilatore deve assumere, che un valore di rvalue e un valore di lvalue possano fare riferimento allo stesso oggetto? Altrimenti, credo, sarebbe permesso riordinare l'ordine di inizializzazione in questo caso. – MikeMB

3

Cosa succede quando si chiama m.emplace(a.s, std::move(a)); è che si sta passando un riferimento di valore l a a.s e un riferimento di valore r a a alla funzione emplace. Se questo è sicuro dipende dall'implementazione di quella funzione. Se per prima cosa elimina il coraggio dal secondo argomento e poi prova a usare il primo argomento, probabilmente avrai un problema. Se prima usa il primo argomento e poi strappa il coraggio dal secondo argomento, nessun problema. Poiché si tratta di una funzione di libreria standard, non è possibile fare affidamento sull'implementazione, quindi non possiamo concludere che il codice sia sicuro.

Per motivi di sicurezza, è possibile assicurarsi che una copia della stringa venga eseguita prima che il contenuto della stringa venga spostato da a.

m.emplace(std::string(a.s), std::move(a)); 

Ora si sta passando un riferimento r-value di una copia temporanea del a.s e un riferimento r-value per a alla funzione colloco. Quindi, è sicuro in questo caso, perché std::map::emplace prende una serie di riferimenti universali.Se si farebbe lo stesso con una funzione che prende il secondo argomento per valore, non è sicuro, perché il costruttore di movimento potrebbe essere chiamato prima che venga eseguita la copia di a.s (poiché l'ordine di valutazione degli argomenti di funzione non è specificato). Sapendo che, questo codice è probabilmente troppo intelligente per essere mantenuto.

Quindi, il codice più chiaro è qualcosa di simile:

auto key = a.s; 
m.emplace(std::move(key), std::move(a)); 
+0

Sei sicuro di 'm.emplace (std :: string (as), std :: move (a)); 'è sicuro? L'ordine di valutazione non è definito indefinito? –

+0

@AlessandroPezzato L'ordine di valutazione non è specificato (non indefinito), ma non influisce sul risultato _in questo caso particolare_. Ho aggiornato la mia risposta per spiegarlo meglio. –

+0

Si dice "è possibile assicurarsi che una copia della stringa venga eseguita prima che il contenuto della stringa venga spostato da un", ma se l'ordine non è specificato, come si è sicuri che la stringa venga copiata prima di essere spostata? –

Problemi correlati