2015-11-20 18 views
16

Dal 2011 abbiamo sia l'assegnazione di copia che quella di spostamento. Tuttavia, this answer argomenta in modo abbastanza convincente che, per le classi di gestione delle risorse, è necessario un solo operatore di assegnazione. Per std::vector, per esempio, questo potrebbe sembrarePerché std :: vector ha due operatori di assegnazione?

vector& vector::operator=(vector other) 
{ 
    swap(other); 
    return*this; 
} 

Il punto importante qui è che l'argomento è preso per valore. Ciò significa che al momento dell'inserimento del corpo della funzione, gran parte del lavoro è già stato fatto con la costruzione di other (se possibile, tramite costruttore di spostamenti, altrimenti con il costruttore di copie). Quindi, questo implementa automaticamente sia l'assegnazione di copia che quella di spostamento in modo corretto.

Se questo è corretto, perché è (secondo this documentation at least) std::vectornon implementato in questo modo?


modifica per spiegare come funziona. Considerare ciò che accade a other in codice di cui sopra nei seguenti esempi

void foo(std::vector<bar> &&x) 
{ 
    auto y=x;    // other is copy constructed 
    auto z=std::move(x); // other is move constructed, no copy is ever made. 
    // ... 
} 
+0

I mi sono anche chiesto questo. Copy-and-swap sembra davvero fantastico. Forse è a causa di motivi legacy, che devono mantenere la firma della funzione esattamente la stessa cosa? Sono curioso di vedere una buona risposta. – MicroVirus

+1

Ciò richiede un'allocazione di memoria. La copia dei contenuti da un lvalue non può, se l'assegnatario ha una capacità sufficiente. – juanchopanza

+0

@juanchopanza L'ho pensato anche io. Se è così, allora la bella risposta di GManNickG non è così carina con le serrature? – Walter

risposta

-2

In realtà ci sono tre operatori di assegnazione definiti:

vector& operator=(const vector& other); 
vector& operator=(vector&& other); 
vector& operator=(std::initializer_list<T> ilist); 

tuo suggerimento vector& vector::operator=(vector other) utilizza la copia e scambiare idioma. Ciò significa che quando viene chiamato l'operatore, il vettore originale verrà copiato nel parametro, copiando ogni singolo elemento nel vettore. Quindi questa copia verrà scambiata con this. Il compilatore potrebbe essere in grado di elidere quella copia, ma quella copia elision è facoltativa, spostare la semantica è standard.

è possibile utilizzare tale linguaggio per sostituire l'operatore di assegnamento copia:

vector& operator=(const vector& other) { 
    swap(vector{other}); // create temporary copy and swap 
    return *this; 
} 

Ogni volta che la copia di qualsiasi elemento tiri, questa funzione getteranno pure.

Per implementare l'operatore di assegnazione in movimento semplicemente lasciare fuori la copia:

vector& operator=(vector&& other) { 
    swap(other); 
    return *this; 
} 

Dal swap() non mai tiri, né sarà l'operatore di assegnazione mossa.

Il initializer_list -assignment può anche essere facilmente implementato utilizzando l'operatore di assegnazione movimento e un temporaneo anonimo:

vector& operator=(std::initializer_list<T> ilist) { 
    return *this = vector{ilist}; 
} 

Abbiamo usato l'operatore di assegnazione mossa. Come concetto, l'operatpr di assegnazione initializer_list si limiterà a gettare solo quando una delle istanze dell'elemento viene lanciata.

Come ho già detto, il compilatore potrebbe essere in grado di eliminare la copia per l'assegnazione della copia. Ma il compilatore non è obbligato a implementare tale ottimizzazione. È obbligato a implementare la semantica del movimento.

+0

Hai effettivamente letto la risposta di cui ho parlato nel mio post? Il punto è che le due implementazioni del trasferimento e l'assegnazione delle copie sono perfettamente e identicamente implementate dall'implementazione * uno * nella mia domanda. – Walter

+0

@Walter che è vero solo quando il compilatore implementa la copia elision. Lo standard fornisce tutta la libertà al fornitore del compilatore di farlo, ma non lo richiede. – cdonat

+0

Quindi stai dicendo che quando una funzione che prende un argomento per valore viene chiamata con un riferimento di rvalue, è legale non muovere ma copiare invece (per inizializzare l'argomento della funzione)? – Walter

7

Se il tipo di elemento è nothrow copiabile, o il contenitore non rispetta la garanzia forte eccezione, poi un operatore di copia-assegnazione può evitare allocazione nel caso in cui l'oggetto di destinazione ha una capacità sufficiente:

vector& operator=(vector const& src) 
{ 
    clear(); 
    reserve(src.size()); // no allocation if capacity() >= src.size() 
    uninitialized_copy_n(src.data(), src.size(), dst.data()); 
    m_size = src.size(); 
} 
+0

Ok, questo è un argomento equo (ed era già coperto nei commenti). Sembra infatti che lo standard C++ non garantisca eccezioni per l'assegnazione delle copie, ma da C++ 17 possibilmente per l'assegnazione del movimento (consentendo l'allocatore). – Walter

Problemi correlati