2010-05-20 14 views
42

Sono confuso con unique_ptr e rvalue move philosophy.Quindi può essere usato unique_ptr in modo sicuro nelle collezioni stl?

Diciamo che abbiamo due collezioni:

std::vector<std::auto_ptr<int>> autoCollection; 
std::vector<std::unique_ptr<int>> uniqueCollection; 

Ora mi aspetterei il seguente a fallire, in quanto non si può dire ciò che l'algoritmo sta facendo internamente e forse l'esecuzione di copie di articolazione interne e simili, strappando così di proprietà da auto_ptr:

std::sort(autoCollection.begin(), autoCollection.end()); 

Ho capito. E il compilatore giustamente nega che questo accada.

Ma poi faccio questo:

std::sort(uniqueCollection.begin(), uniqueCollection.end()); 

E questo compila. E non capisco perché. Non pensavo che unique_ptrs potesse essere copiato. Questo significa che non è possibile utilizzare un valore pivot, quindi l'ordinamento è meno efficiente? Oppure questo pivot è in realtà una mossa, che di fatto è pericolosa quanto la raccolta di auto_ptrs, e dovrebbe essere disabilitata dal compilatore?

Penso che mi manchi qualche informazione cruciale, quindi attendo con impazienza qualcuno che mi fornisca l'aha! momento.

+1

In realtà il compilatore * dovrebbe * lamentarsi di 'std :: vector > autoCollection;' perché COAPS (Contenitori di Puntatori automatici) non sono consentiti in nessuna descrizione. –

+0

Utilizzo di VS2010. Non ho nemmeno ricevuto un avviso su/W4. – DanDan

+1

o ricevi un avviso che auto_ptr è ammortizzato. – DanDan

risposta

50

penso che sia più una questione di filosofia di tecnica :)

La domanda di fondo è ciò che è la differenza tra spostare e copiare. Non voglio saltare in linguaggio tecnico/standardista, facciamolo semplicemente:

  • Copy: creare un altro oggetto identico (o almeno, quella che dovrebbe confrontare uguali)
  • Sposta: prendere un oggetto e metterlo in un'altra posizione

Come hai detto, è possibile implementare Sposta in termini di copia: creare una copia nella nuova posizione e scartare l'originale. Tuttavia ci sono due problemi lì. Uno è di prestazioni, il secondo riguarda gli oggetti usati per RAII: quale dei due dovrebbe avere la proprietà?

Una corretta costruttore Spostare risolve i 2 problemi:

  • E 'chiaro che oggetto ha proprietà: quello nuovo, dal momento che l'originale sarà scartato
  • Non è quindi necessario per copiare le risorse indicavano , che permette una maggiore efficienza

Il auto_ptr e unique_ptr sono un buon esempio di questo.

Con un auto_ptr si ha una semantica Copia avvitata: l'originale e la copia non sono uguali.Potresti usarlo per la sua semantica di Move ma c'è il rischio che tu perda l'oggetto puntato da qualche parte.

D'altra parte, lo unique_ptr è esattamente questo: garantisce un proprietario unico della risorsa, evitando così la copia e l'inevitabile problema di eliminazione che ne conseguirebbe. E la no-copia è garantita anche in fase di compilazione. Pertanto, è adatto nei contenitori purché non si tenti di avere l'inizializzazione della copia.

typedef std::unique_ptr<int> unique_t; 
typedef std::vector<unique_t> vector_t; 

vector_t vec1;       // fine 
vector_t vec2(5, unique_t(new Foo));  // Error (Copy) 
vector_t vec3(vec1.begin(), vec1.end()); // Error (Copy) 
vector_t vec3(make_move_iterator(vec1.begin()), make_move_iterator(vec1.end())); 
    // Courtesy of sehe 

std::sort(vec1.begin(), vec1.end()); // fine, because using Move Assignment Operator 

std::copy(vec1.begin(), vec1.end(), std::back_inserter(vec2)); // Error (copy) 

Così si può utilizzare unique_ptr in un contenitore (a differenza auto_ptr), ma una serie di operazioni sarà impossibile perché coinvolgono la copia che il tipo non supporta.

Sfortunatamente Visual Studio può essere abbastanza lassista nell'applicazione dello standard e ha anche un numero di estensioni che dovresti disabilitare per garantire la portabilità del codice ... non usarlo per controllare lo standard :)

+0

Grazie, ottima risposta ed esempi :) – DanDan

+3

Nel tuo commento "bene, perché usando il costruttore di Move" intendevi "move assignment o 'swap'"? Inoltre, per completezza, ecco come si possono fare gli 'impossibili' usando 'std :: move' e' std :: make_move_iterator': http://coliru.stacked-crooked.com/a/6b032d70a9ed6f5c – sehe

+0

@sehe: sei a destra, intendevo l'operatore di assegnazione delle mosse. –

11

Gli unique_ptr vengono spostati usando il loro costruttore di movimento. unique_ptr è mobile, ma non CopyConstructable.

C'è un grande articolo sui riferimenti di rvalore here. Se non hai ancora letto su di loro, o sei confuso, dai un'occhiata!

+0

Hey grazie per l'ottimo articolo! È stato un po 'folle in giro per pagina 7, ma ho imparato molto. Ma il mio problema originale rimane Perché le mosse sono sicure eppure le copie no? Non è una mossa di unique_ptr come la copia di auto_ptr? Se il costruttore di movimento dell'oggetto utilizza std :: move, ovvero il trasferimento di proprietà, non è questo il comportamento predefinito di una copia auto_ptr? – DanDan

+1

Sì, ma auto_ptr era prima del C++ 0x ed è espressamente non consentito in container D'altra parte, poiché unique_ptr può essere spostato senza problemi, tu (e il compilatore) puoi stare certi che la proprietà non è violato Inoltre, tieni presente che l'algoritmo di ordinamento STL non è specificato e in C++ 0x è necessario solo spostare le cose. Tuttavia è possibile eseguire quicksort sul posto, quindi non sono richieste copie. – rlbond

7

std::sort potrebbe funzionare solo con le operazioni di spostamento e senza copiare, purché vi sia una sola copia live di ciascun oggetto in un dato momento. Questo è un requisito più debole rispetto al lavoro sul posto, dal momento che in linea di principio è possibile allocare temporaneamente un altro array e spostare tutti gli oggetti durante il loro riordino.

ad esempio con std::vector<std::unique_ptr<T>> che supera la sua capacità, alloca lo spazio di archiviazione per un vettore più grande e quindi sposta tutti gli oggetti dal vecchio spazio di archiviazione a quello nuovo. Questa non è un'operazione sul posto, ma è perfettamente valida.

Come risulta, gli algoritmi di ordinamento come quick-sort e heap-sort possono infatti funzionare sul posto senza difficoltà. la routine di partizionamento rapido usa std :: swap internamente, che conta come un'operazione di spostamento per entrambi gli oggetti coinvolti. Quando si seleziona un pivot, un trucco è scambiarlo con il primo elemento dell'intervallo, in questo modo non verrà mai spostato fino al completamento del partizionamento.

Problemi correlati