2012-05-07 11 views
6

Cosa serve per utilizzare l'operatore di assegnazione spostamento di std :: string (in VC11)?Spostamento: cosa serve?

Spero che venga utilizzato automaticamente poiché v non è più necessario dopo l'assegnazione. Std: move richiesto in questo caso? Se è così, potrei anche usare lo swap non-C++ 11.

#include <string> 

struct user_t 
{ 
    void set_name(std::string v) 
    { 
     name_ = v; 
     // swap(name_, v); 
     // name_ = std::move(v); 
    } 

    std::string name_; 
}; 

int main() 
{ 
    user_t u; 
    u.set_name("Olaf"); 
    return 0; 
} 
+2

non vedo un costruttore mossa da qualche parte? – Corbin

+0

@NicolBolas: questo non è correlato in remoto a questa domanda. Sta chiedendo di spostare un membro 'std :: string' della sua classe. Non sta cercando di spostare la classe. –

+0

@MooingDuck: Oh. Bene, non importa poi. Non posso davvero fare molto per il voto vicino se ... –

risposta

11

Spero che venga utilizzato automaticamente poiché v non è più necessario dopo l'assegnazione. Std :: move richiesto in questo caso?

Il movimento deve sempre essere specificato esplicitamente per lvalue, a meno che non vengano restituiti (in base al valore) da una funzione.

Ciò impedisce lo spostamento accidentale di qualcosa. Ricorda: il movimento è un atto distruttivo; non vuoi che accada semplicemente.

Inoltre, sarebbe strano se la semantica di name_ = v; fosse cambiata in base al fatto che si trattasse dell'ultima riga di una funzione. Dopo tutto, questo è il codice perfettamente legale:

name_ = v; 
v[0] = 5; //Assuming v has at least one character. 

Perché dovrebbe la prima linea di eseguire una copia a volte e una mossa altre volte?

In tal caso, è consigliabile utilizzare lo swap non C++ 11.

Si può fare come volete, ma std::move è più ovvio per quanto riguarda l'intento. Sappiamo cosa significa e cosa stai facendo con esso.

+0

Move è fondamentalmente un'ottimizzazione della copia. Quindi, sarebbe bello se il compilatore eseguisse automaticamente questa ottimizzazione se rileva che è sicuro farlo. – XTF

+0

@XTF: Ciò renderebbe difficile sapere quale codice farà, se chiamerà copia o sposta costruttore. Elision fa già qualcosa di simile, ma succede solo in circostanze molto specifiche. E anche allora, quando elision non accade, si sa ancora esattamente quale costruttore viene chiamato. –

+0

La regola da tenere presente qui è che qualsiasi cosa con un nome è un lvalue e il movimento implicito può essere eseguito solo su valori rvalori. –

5

Nell'esempio, set_name prende la stringa in base al valore. All'interno di set_name, tuttavia, v è un lvalue. Diamo trattare questi casi separatamente:

user_t u; 
std::string str("Olaf"); // Creates string by copying a char const*. 
u.set_name(std::move(str)); // Moves string. 

All'interno set_name si richiama l'operatore di assegnazione di std::string, che comporta una copia inutile. Ma c'è anche un rvalue overload di operator=, che rende più senso nel tuo caso:

void set_name(std::string v) 
{ 
    name_ = std::move(v); 
} 

In questo modo, l'unica copia che si svolge è la stringa di constrution (std::string("Olaf")).

+0

Spostare un argomento passato per valore qui non ha senso. Stai evitando una copia, ma hai già copiato la stringa iniziale nell'argomento passato per valore. – mfontanini

+1

@fontanini: è un'opzione comune perché è abbastanza performante sia per il passaggio di rvalue che di lvalue entrambi, in una funzione. –

+3

Per espandere @ La risposta di MooingDuck: Usando la semantica del valore giusto, spetta al * chiamante * decidere se incorrere in una mossa o copia. – mavam

7

La risposta accettata è una buona risposta (e l'ho svalutato). Ma ho voluto affrontare la questione in un po 'più in dettaglio:

Il nucleo della mia domanda è: Perché non scegliere l'operatore di assegnazione mossa automaticamente? Il compilatore sa che v non viene utilizzato dopo l'assegnazione , non è vero? O C++ 11 non richiede che il compilatore sia così intelligente?

Questa possibilità è stata esaminata durante la progettazione della semantica del movimento.A un estremo, si potrebbe desiderare il compilatore di fare qualche analisi statica e passare da oggetti quando possibile:

void set_name(std::string v) 
{ 
    name_ = v; // move from v if it can be proven that some_event is false? 
    if (some_event) 
     f(v); 
} 

definitiva chiedendo questo tipo di analisi del compilatore è molto difficile. Alcuni compilatori potrebbero essere in grado di fare la dimostrazione e altri no. Portando così al codice che non è davvero portatile.

Ok, allora che dire di alcuni casi più semplici, senza istruzioni if?

void foo() 
{ 
    X x; 
    Y y(x); 
    X x2 = x; // last use? move? 
} 

Beh, è ​​difficile sapere se y.~Y() noterà x è stato spostato da. E in generale:

void foo() 
{ 
    X x; 
    // ... 
    // lots of code here 
    // ... 
    X x2 = x; // last use? move? 
} 

è difficile per il compilatore di analizzare questo per sapere se x è veramente non è più utilizzato dopo la costruzione copia x2.

Quindi la proposta iniziale "mossa" ha dato una regola per i movimenti impliciti che è stato davvero semplice, e molto conservatrice:

lvalue possono essere implicitamente spostati solo nei casi in cui da copiare elisione è già consentita.

Ad esempio:

#include <cassert> 

struct X 
{ 
    int i_; 
    X() : i_(1) {} 
    ~X() {i_ = 0;} 
}; 

struct Y 
{ 
    X* x_; 
    Y() : x_(0) {} 
    ~Y() {assert(x_ != 0); assert(x_->i_ != 0);} 
}; 

X foo(bool some_test) 
{ 
    Y y; 
    X x; 
    if (some_test) 
    { 
     X x2; 
     return x2; 
    } 
    y.x_ = &x; 
    return x; 
} 

int main() 
{ 
    X x = foo(false); 
} 

Qui, da C++ 98/03 regole, questo programma può o non può affermare, a seconda se o meno copiare elisione a return x succede. Se succede, il programma funziona correttamente. Se non succede, afferma il programma.

E così è stato ragionato: quando è permesso RVO, siamo già in una zona dove non ci sono garanzie per quanto riguarda il valore di x. Quindi dovremmo essere in grado di sfruttare questo margine e passare da x. Il rischio sembrava piccolo e il beneficio sembrava enorme. Non solo questo significherebbe che molti programmi esistenti diventerebbero molto più velocemente con una semplice ricompilazione, ma significava anche che ora potevamo tornare "muovere solo" tipi da funzioni factory. Questo è un rapporto beneficio/rischio molto elevato.

Ultimamente nel processo di standardizzazione, siamo diventati un po 'avidi e abbiamo anche detto che la mossa implicita si verifica quando si restituisce un parametro in base al valore (e il tipo corrisponde al tipo restituito). Anche qui i benefici sembrano relativamente grandi, anche se la possibilità di interruzione del codice è leggermente maggiore poiché questo non è il caso in cui RVO era (o è) legale. Ma non ho una dimostrazione di codice di rottura per questo caso.

Quindi in ultima analisi, la risposta alla tua domanda di base è che il disegno originale della semantica mossa ha preso una strada molto conservatore rispetto a rompere il codice esistente. Se non fosse stato, sarebbe sicuramente stato abbattuto in commissione. Più tardi nel processo, ci sono stati alcuni cambiamenti che hanno reso il design un po 'più aggressivo. Ma a quel tempo la proposta principale era saldamente radicata nello standard con un sostegno maggioritario (ma non unanime).