C'è un'immagine ben nota (cheat sheet) chiamata "Scelta del contenitore C ++". È un diagramma di flusso per scegliere il contenitore migliore per l'uso desiderato.
Qualcuno sa se esiste già una versione C ++ 11 di esso?
Questo è il precedente:
C'è un'immagine ben nota (cheat sheet) chiamata "Scelta del contenitore C ++". È un diagramma di flusso per scegliere il contenitore migliore per l'uso desiderato.
Qualcuno sa se esiste già una versione C ++ 11 di esso?
Questo è il precedente:
Risposte:
Non che io lo sappia, comunque immagino che possa essere fatto testualmente. Inoltre, il grafico è leggermente fuori, perché list
non è un contenitore così buono in generale, e nemmeno lo è forward_list
. Entrambi gli elenchi sono contenitori molto specializzati per applicazioni di nicchia.
Per creare un grafico del genere, hai solo bisogno di due semplici linee guida:
Inizialmente preoccuparsi delle prestazioni è di solito inutile. Le grandi considerazioni sulla O entrano davvero in gioco solo quando inizi a gestire alcune migliaia (o più) di oggetti.
Esistono due grandi categorie di contenitori:
find
un'operazionee poi si può costruire diversi adattatori su di essi: stack
, queue
, priority_queue
. Lascerò qui gli adattatori, sono sufficientemente specializzati per essere riconoscibili.
Domanda 1: Associativa ?
Domanda 1.1: Ordinata ?
unordered_
container, altrimenti usa la sua controparte ordinata tradizionale.Domanda 1.2: Chiave separata ?
map
, altrimenti utilizzare aset
Domanda 1.3: duplicati ?
multi
, altrimenti non.Esempio:
Supponiamo che io abbia diverse persone a cui è associato un ID univoco e che vorrei recuperare i dati di una persona dal suo ID nel modo più semplice possibile.
Voglio una find
funzione, quindi un contenitore associativo
1.1. Non me ne potrebbe fregare di meno dell'ordine, quindi un unordered_
contenitore
1.2. La mia chiave (ID) è separata dal valore a cui è associata, quindi amap
1.3. L'ID è univoco, quindi nessun duplicato dovrebbe insinuarsi.
La risposta finale è: std::unordered_map<ID, PersonData>
.
Domanda 2: memoria stabile ?
list
Domanda 2.1: Quale ?
list
; a forward_list
è utile solo per un minore footprint di memoria.Domanda 3: dimensioni dinamiche ?
{ ... }
sintassi), quindi utilizzare un array
. Sostituisce il tradizionale C-array, ma con comode funzioni.Domanda 4: doppio attacco ?
deque
, altrimenti usa a vector
.Noterai che, per impostazione predefinita, a meno che tu non abbia bisogno di un contenitore associativo, la tua scelta sarà a vector
. Si scopre che è anche la raccomandazione di Sutter e Stroustrup .
array
non richiede un tipo costruibile predefinito; 2) La scelta dei messaggi di posta multi
elettronica non dipende tanto dal fatto che i duplicati sono consentiti, ma piuttosto dal fatto che mantenerli importanti (puoi mettere i duplicati in non multi
contenitori, succede solo che ne viene conservato solo uno).
map.find(key)
è molto più appetibile di std::find(map.begin(), map.end(), [&](decltype(map.front()) p) { return p.first < key; }));
quanto non sia, quindi è importante, semanticamente, che find
sia una funzione membro piuttosto che quella di <algorithm>
. Per quanto riguarda O (1) vs O (log n), non influisce sulla semantica; Rimuoverò "in modo efficiente" dall'esempio e lo sostituirò con "facilmente".
deque
avessi anche questa proprietà?
deque
gli elementi sono stabili solo se si preme / pop alle due estremità; se si inizia a inserire / cancellare nel mezzo, fino a N / 2 elementi vengono mescolati per riempire lo spazio creato.
Mi piace la risposta di Matthieu, ma ho intenzione di riaffermare il diagramma di flusso come questo:
Per impostazione predefinita, se hai bisogno di un contenitore di roba, usa std::vector
. Pertanto, ogni altro contenitore è giustificato solo fornendo un'alternativa di funzionalità a std::vector
.
std::vector
richiede che il suo contenuto sia costruibile in movimento, poiché deve essere in grado di mescolare gli oggetti in giro. Questo non è un onere gravoso da porre sui contenuti (si noti che i costruttori predefiniti non sono richiesti , grazie emplace
e così via). Tuttavia, la maggior parte degli altri contenitori non richiede alcun costruttore particolare (di nuovo, grazie a emplace
). Quindi, se si dispone di un oggetto in cui è assolutamente non si può implementare un costruttore mossa, allora si dovrà scegliere qualcos'altro.
A std::deque
sarebbe il rimpiazzo generale, avente molte delle proprietà di std::vector
, ma è possibile inserire solo su entrambe le estremità del deque. Gli inserti nel mezzo richiedono spostamento. Un std::list
posto non richiede alcun contenuto.
std::vector<bool>
non è. Bene, è standard. Ma non è vector
nel solito senso, poiché le operazioni che std::vector
normalmente consentono sono vietate. E sicuramente non contiene bool
s .
Pertanto, se hai bisogno di un vector
comportamento reale da un contenitore di bool
s, non lo otterrai std::vector<bool>
. Quindi dovrai fare il dovuto con a std::deque<bool>
.
Se è necessario trovare elementi in un contenitore e il tag di ricerca non può essere solo un indice, potrebbe essere necessario abbandonare std::vector
a favore di set
e map
. Nota la parola chiave " may "; a std::vector
volte un ordinato è un'alternativa ragionevole. O Boost.Container's flat_set/map
, che implementa un ordinato std::vector
.
Esistono ora quattro varianti di queste, ognuna con le proprie esigenze.
map
quando il tag di ricerca non è uguale all'elemento che stai cercando. Altrimenti usa a set
.unordered
quando sono presenti molti elementi nel contenitore e le prestazioni di ricerca devono assolutamente essere O(1)
, anziché O(logn)
.multi
se sono necessari più elementi per avere lo stesso tag di ricerca.Se è necessario ordinare sempre un contenitore di articoli in base a una particolare operazione di confronto, è possibile utilizzare a set
. Oppure multi_set
se hai bisogno di più articoli per avere lo stesso valore.
Oppure puoi usare un ordinato std::vector
, ma dovrai mantenerlo ordinato.
Quando gli iteratori e i riferimenti vengono invalidati, a volte è un problema. Se hai bisogno di un elenco di elementi, in modo tale da avere iteratori / puntatori a quegli elementi in vari altri posti, std::vector
l'approccio all'invalidazione potrebbe non essere appropriato. Qualsiasi operazione di inserimento può causare invalidazioni, a seconda delle dimensioni e della capacità correnti.
std::list
offre una garanzia certa: un iteratore e i relativi riferimenti / puntatori vengono invalidati solo quando l'elemento stesso viene rimosso dal contenitore. std::forward_list
c'è se la memoria è una preoccupazione seria.
Se è una garanzia troppo forte, std::deque
offre una garanzia più debole ma utile. L'invalidazione deriva dagli inserimenti nel mezzo, ma gli inserimenti nella testa o nella coda causano solo l'invalidazione degli iteratori , non dei puntatori / riferimenti agli elementi nel contenitore.
std::vector
fornisce solo un inserimento economico alla fine (e anche allora, diventa costoso se si soffia la capacità).
std::list
è costoso in termini di prestazioni (ogni elemento appena inserito costa un'allocazione di memoria), ma è coerente . Offre inoltre la possibilità occasionalmente indispensabile di mescolare gli oggetti praticamente senza costi di prestazione, nonché di scambiare oggetti con altri std::list
contenitori dello stesso tipo senza perdita di prestazioni. Se devi mescolare molte cose , usa std::list
.
std::deque
fornisce inserimento / rimozione a tempo costante nella testa e nella coda, ma l'inserimento nel mezzo può essere piuttosto costoso. Quindi, se hai bisogno di aggiungere / rimuovere oggetti dalla parte anteriore e posteriore, std::deque
potrebbe essere quello che ti serve.
Va notato che, grazie allo spostamento della semantica, le std::vector
prestazioni di inserimento potrebbero non essere peggiori come una volta. Alcune implementazioni hanno implementato una forma di copia degli oggetti basata su semantiche di movimento (la cosiddetta "swaptimization"), ma ora che lo spostamento fa parte del linguaggio, è obbligatorio per lo standard.
std::array
è un ottimo contenitore se si desidera il minor numero possibile di allocazioni dinamiche. È solo un involucro attorno a un array C; questo significa che le sue dimensioni devono essere conosciute in fase di compilazione . Se riesci a convivere con quello, allora usa std::array
.
Detto questo, usare std::vector
e reserve
ing di una dimensione funzionerebbe altrettanto bene per un limite std::vector
. In questo modo, le dimensioni effettive possono variare e si ottiene solo un'allocazione di memoria (a meno che non si esaurisca la capacità).
std::sort
, c'è anche std::inplace_merge
che è interessante posizionare facilmente nuovi elementi (piuttosto che una std::lower_bound
+ std::vector::insert
chiamata). Bello da imparare flat_set
e flat_map
!
vector<bool>
è vector<char>
.
std::allocator<T>
non supporta tale allineamento (e non so perché non lo farebbe), puoi sempre utilizzare il tuo allocatore personalizzato.
std::vector::resize
ha un sovraccarico che non ha valore (richiede solo la nuova dimensione; tutti i nuovi elementi saranno costruiti sul posto di default). Inoltre, perché i compilatori non sono in grado di allineare correttamente i parametri di valore, anche quando viene dichiarato che hanno tale allineamento?
bitset
per bool se conosci le dimensioni in anticipo en.cppreference.com/w/cpp/utility/bitset
Ecco la versione C ++ 11 del diagramma di flusso sopra. [originariamente pubblicato senza attribuzione al suo autore originale, Mikael Persson ]
Ecco un giro veloce, anche se probabilmente ha bisogno di lavoro
Should the container let you manage the order of the elements?
Yes:
Will the container contain always exactly the same number of elements?
Yes:
Does the container need a fast move operator?
Yes: std::vector
No: std::array
No:
Do you absolutely need stable iterators? (be certain!)
Yes: boost::stable_vector (as a last case fallback, std::list)
No:
Do inserts happen only at the ends?
Yes: std::deque
No: std::vector
No:
Are keys associated with Values?
Yes:
Do the keys need to be sorted?
Yes:
Are there more than one value per key?
Yes: boost::flat_map (as a last case fallback, std::map)
No: boost::flat_multimap (as a last case fallback, std::map)
No:
Are there more than one value per key?
Yes: std::unordered_multimap
No: std::unordered_map
No:
Are elements read then removed in a certain order?
Yes:
Order is:
Ordered by element: std::priority_queue
First in First out: std::queue
First in Last out: std::stack
Other: Custom based on std::vector?????
No:
Should the elements be sorted by value?
Yes: boost::flat_set
No: std::vector
Potresti notare che questo differisce enormemente dalla versione C ++ 03, principalmente perché non mi piacciono i nodi collegati. I contenitori di nodi collegati possono in genere essere battuti nelle prestazioni da un contenitore non collegato, tranne in alcune rare situazioni. Se non sai quali siano queste situazioni e hai accesso a boost, non utilizzare contenitori di nodi collegati. (std :: list, std :: slist, std :: map, std :: multimap, std :: set, std :: multiset). Questo elenco si concentra principalmente su contenitori di piccole e medie dimensioni, perché (A) è il 99,99% di ciò che trattiamo nel codice e (B) Un gran numero di elementi richiede algoritmi personalizzati, non contenitori diversi.