In quale scenario utilizzo un contenitore STL particolare?


185

Ho letto sui contenitori STL nel mio libro su C ++, in particolare la sezione sulla STL e sui suoi contenitori. Ora capisco che ognuno di loro ha le proprie proprietà specifiche e sono vicino a memorizzarli tutti ... Ma ciò che non afferro ancora è in quale scenario viene usato ciascuno di essi.

Qual è la spiegazione? Il codice di esempio è molto preferito.


vuoi dire mappa, vectot, impostare ecc?
Thomas Tempelmann,

Anche guardando a questo schema non posso dire quale sarebbe il migliore per l'uso nel mio quastion stackoverflow.com/questions/9329011/...
sergiol

2
@sbi: rimozione del tag Faq C ++ da questo e aggiunta al più recente e incluso C ++ 11 Come posso selezionare in modo efficiente un contenitore di libreria standard in C ++ 11?
Alok Save,

Risposte:


338

Questo cheat sheet fornisce un riassunto abbastanza buono dei diversi contenitori.

Vedi il diagramma di flusso in basso come guida su cui utilizzare in diversi scenari di utilizzo:

http://linuxsoftware.co.nz/containerchoice.png

Creato da David Moore e concesso in licenza CC BY-SA 3.0


14
Questo diagramma di flusso è dorato, vorrei avere qualcosa del genere in c #
Bruno,


3
Il punto di partenza deve essere vectorpiuttosto che vuoto. stackoverflow.com/questions/10699265/...
eonil

5
Ora hai unordered_mape unordered_set(e le loro multi varianti) che non sono nel diagramma di flusso ma sono una buona scelta quando non ti interessa l'ordine ma devi trovare elementi per chiave. La loro ricerca è generalmente O (1) invece di O (log n).
Aidiakapi,

2
@ shuttle87 non solo la dimensione non varierà mai, ma soprattutto quella dimensione viene determinata al momento della compilazione e non varierà mai.
Young John

188

Ecco un diagramma di flusso ispirato alla versione di David Moore (vedi sopra) che ho creato, che è aggiornato (principalmente) con il nuovo standard (C ++ 11). Questa è solo la mia opinione personale, non è indiscutibile, ma ho pensato che potesse essere utile a questa discussione:

inserisci qui la descrizione dell'immagine


4
Puoi rendere disponibile l'originale? È un grafico eccellente. Forse attenersi a un blog o GitHub?
kevinarpe,

1
Questo è un grafico eccellente. Qualcuno può spiegarmi cosa si intende per "posizioni persistenti"?
IDDQD

3
@STALKER Posizioni persistenti significa che se si dispone di un puntatore o iteratore su un elemento nel contenitore, quel puntatore o iteratore rimarrà valido (e punta allo stesso elemento) indipendentemente da ciò che si aggiunge o si rimuove dal contenitore (purché non è l'elemento in questione).
Mikael Persson,

1
Questo è davvero un ottimo grafico, tuttavia penso che vector (sorted)sia un po 'incoerente con il resto. Non è un diverso tipo di contenitore, è lo stesso std::vectorma ordinato. Ancora più importante, non vedo perché non si possa usare a std::setper l'iterazione ordinata se questo è il comportamento standard dell'iterazione attraverso un set. Certo, se la risposta parla dell'accesso ordinato ai valori del contenitore [], allora ok puoi farlo solo con un voto std::vector. Ma in entrambi i casi, la decisione dovrebbe essere presa subito dopo la domanda "è necessario un ordine"
RA

1
@ user2019840 Volevo limitare il grafico ai contenitori standard. Ciò che dovrebbe apparire al posto del "vettore ordinato" è "flat_set" (da Boost.Container ), o equivalente (ogni libreria principale o base di codice ha un equivalente flat_set, AFAIK). Ma questi sono non standard e un'omissione lampante dall'STL. E il motivo per cui non si desidera scorrere attraverso std :: set o std :: map (almeno non frequentemente) è che è molto inefficiente farlo .
Mikael Persson,

41

Risposta semplice: usa std::vectorper tutto a meno che tu non abbia una vera ragione per fare diversamente.

Quando trovi un caso in cui stai pensando "Accidenti, std::vectornon funziona bene qui a causa di X", vai sulla base di X.


1
Comunque .. fai attenzione a non cancellare / inserire elementi durante l'iterazione ... usa const_iterator per quanto possibile per evitare questo ..
vrdhn

11
Hmm ... Penso che le persone usino troppo il vettore. Il motivo è che il caso "non funziona" non accadrà facilmente, quindi le persone si attengono al contenitore più usato e lo usano in modo improprio per archiviare elenchi, code, ... A mio avviso - che corrisponde al diagramma di flusso - si dovrebbe scegliere il contenitore in base all'uso previsto invece di applicare "sembra che si adatti a tutti".
Nero,

13
@ Black Point è che il vettore è solitamente più veloce anche su operazioni che in teoria dovrebbero funzionare più lentamente.
Bartek Banachewicz,

1
@Vardhan std::remove_ifè quasi sempre superiore all'approccio "elimina durante l'iterazione".
Fredoverflow,

1
Alcuni benchmark aiuterebbero davvero questa discussione ad essere meno soggettiva.
Felix D.

11

Guarda Effective STL di Scott Meyers. È bravo a spiegare come utilizzare STL.

Se vuoi memorizzare un numero determinato / indeterminato di oggetti e non ne eliminerai mai uno, allora un vettore è quello che vuoi. È la sostituzione predefinita per un array C e funziona come uno, ma non trabocca. Puoi anche impostare le sue dimensioni in anticipo con reserve ().

Se vuoi memorizzare un numero indeterminato di oggetti, ma li aggiungerai e li cancellerai, allora probabilmente vorrai un elenco ... perché puoi eliminare un elemento senza spostare alcun elemento seguente - a differenza del vettore. Tuttavia, richiede più memoria di un vettore e non è possibile accedere in sequenza a un elemento.

Se vuoi prendere un gruppo di elementi e trovare solo i valori univoci di quegli elementi, leggerli tutti in un set lo farà e li ordinerà anche per te.

Se hai molte coppie chiave-valore e vuoi ordinarle per chiave, una mappa è utile ... ma conterrà solo un valore per chiave. Se hai bisogno di più di un valore per chiave, potresti avere un vettore / elenco come valore nella mappa o utilizzare una multimappa.

Non è nell'STL, ma è nell'aggiornamento TR1 dell'STL: se hai molte coppie chiave-valore che cercherai per chiave e non ti interessa il loro ordine, potresti vuoi usare un hash - che è tr1 :: unordered_map. L'ho usato con Visual C ++ 7.1, dove si chiamava stdext :: hash_map. Ha una ricerca di O (1) invece di una ricerca di O (log n) per la mappa.


Ho sentito un paio di aneddoti che suggeriscono che Microsoft hash_mapnon è un'implementazione molto buona. Spero che abbiano fatto meglio unordered_map.
Mark Ransom,

3
Di elenchi: "non è possibile accedere in sequenza a un elemento". - Penso che intendi dire che non puoi accedere in modo casuale o indicizzare direttamente un elemento ....
Tony Delroy

^ Sì, perché l'accesso sequenziale è esattamente ciò che listfa. Piuttosto evidente errore lì.
underscore_d

7

Ho riprogettato il diagramma di flusso per avere 3 proprietà:

  1. Penso che i contenitori STL siano divisi in 2 classi principali. I contenitori di base e quelli sfruttano i contenitori di base per attuare una politica.
  2. Inizialmente il diagramma di flusso dovrebbe dividere il processo decisionale per le principali situazioni su cui dovremmo decidere e quindi elaborare ogni caso.
  3. Alcuni contenitori estesi hanno la possibilità di scegliere diversi contenitori base come loro contenitore interno. Il diagramma di flusso dovrebbe considerare le situazioni in cui è possibile utilizzare ciascuno dei contenitori di base.

Il diagramma di flusso: inserisci qui la descrizione dell'immagine

Maggiori informazioni fornite in questo link .


5

Un punto importante solo brevemente accennato finora, è che se si richiede memoria contigua (come una matrice C figura), allora è possibile utilizzare solo vector, arrayo string.

Utilizzare arrayse la dimensione è nota al momento della compilazione.

Utilizzare stringse è necessario solo lavorare con tipi di carattere e è necessaria una stringa, non solo un contenitore per uso generico.

Utilizzare vectorin tutti gli altri casi ( vectordovrebbe comunque essere la scelta predefinita del contenitore nella maggior parte dei casi).

Con tutti e tre questi è possibile utilizzare la data()funzione membro per ottenere un puntatore al primo elemento del contenitore.


3

Tutto dipende da cosa vuoi conservare e cosa vuoi fare con il contenitore. Ecco alcuni esempi (molto non esaustivi) per le classi di container che tendo a utilizzare maggiormente:

vector: Layout compatto con sovraccarico di memoria minimo o nullo per oggetto contenuto. Efficiente da ripetere. Aggiungere, inserire e cancellare può essere costoso, in particolare per oggetti complessi. Economico per trovare un oggetto contenuto in base all'indice, ad esempio myVector [10]. Usa dove avresti usato un array in C. Buono dove hai molti oggetti semplici (es. Int). Non dimenticare di usare reserve()prima di aggiungere molti oggetti al contenitore.

list: Sovraccarico di memoria ridotto per oggetto contenuto. Efficiente da ripetere. Aggiungi, inserisci e cancella sono economici. Usa dove avresti usato un elenco collegato in C.

set(e multiset): sovraccarico di memoria significativo per oggetto contenuto. Utilizzare dove è necessario scoprire rapidamente se quel contenitore contiene un determinato oggetto o unire i contenitori in modo efficiente.

map(e multimap): sovraccarico di memoria significativo per oggetto contenuto. Utilizzare dove si desidera memorizzare coppie chiave-valore e cercare rapidamente i valori per chiave.

Il diagramma di flusso sul cheat sheet suggerito da zdan fornisce una guida più esaustiva.


"L'overhead di memoria piccolo per oggetto contenuto" non è vero per l'elenco. std :: list è implementato come elenco doppiamente collegato e quindi mantiene 2 puntatori per oggetto memorizzato che non deve essere trascurato.
Hanna Khalil,

Conterrei ancora due puntatori per oggetto memorizzato come "piccoli".
Offerte del

rispetto a cosa? std :: forward_list è un contenitore che è stato principalmente suggerito di avere meno metadati memorizzati per oggetto (solo un puntatore). Mentre std :: vector contiene 0 metadati per oggetto. Quindi 2 puntatori non sono negoziabili rispetto ad altri container
Hanna Khalil,

Tutto dipende dalla dimensione dei tuoi oggetti. Ho già detto che il vettore ha un "layout compatto con sovraccarico di memoria minimo o nullo per oggetto contenuto". Vorrei ancora dire che l'elenco ha un piccolo overhead di memoria rispetto a set e mappa e un overhead di memoria leggermente più grande del vettore. Non sono sicuro di che punto stai cercando di realizzare TBH!
Offerte

Tutti i contenitori basati sulla modalità tendono ad avere un notevole sovraccarico a causa dell'allocazione dinamica, che raramente arriva gratuitamente. A meno che ovviamente non si stia utilizzando un allocatore personalizzato.
MikeMB,

2

Una lezione che ho imparato è: prova a racchiuderlo in una classe, poiché cambiare il tipo di contenitore in una bella giornata può portare grandi sorprese.

class CollectionOfFoo {
    Collection<Foo*> foos;
    .. delegate methods specifically 
}

Non costa molto in anticipo e consente di risparmiare tempo nel debug quando si desidera interrompere ogni volta che qualcuno esegue l'operazione x su questa struttura.

Venendo a selezionare la struttura dati perfetta per un lavoro:

Ogni struttura di dati fornisce alcune operazioni, che possono variare in base alla complessità temporale:

O (1), O (lg N), O (N), ecc.

In sostanza, devi provare a indovinare meglio quali operazioni verranno eseguite di più e utilizzare una struttura di dati che ha tale operazione come O (1).

Semplice, no (-:


5
Non è per questo che usiamo gli iteratori?
Platinum Azure

@PlatinumAzure Anche gli iteratori dovrebbero essere membri typedef .. Se cambi il tipo di contenitore devi anche andare e cambiare tutte le definizioni di iteratore ... questo però è stato corretto in c ++ 1x!
vrdhn,

4
Per i curiosi, questa è la correzione in C ++ 11: auto myIterator = whateverCollection.begin(); // <-- immune to changes of container type
Nero

1
Sarebbe typedef Collection<Foo*> CollectionOfFoo;sufficiente?
Craig McQueen,

5
È abbastanza improbabile che tu possa semplicemente cambiare idea in seguito e delegare semplicemente a un altro contenitore:
fai


1

Ho risposto a questa domanda in un'altra domanda che è contrassegnata come duplice di questa. Ma penso che sia bello fare riferimento ad alcuni buoni articoli riguardanti la decisione di scegliere un contenitore standard.

Come ha risposto @David Thornley, std :: vector è la strada da percorrere se non ci sono altre esigenze speciali. Questo è il consiglio dato dal creatore di C ++, Bjarne Stroustrup in un blog del 2014.

Ecco il link per l'articolo https://isocpp.org/blog/2014/06/stroustrup-lists

e citare da quello,

E, sì, la mia raccomandazione è di usare std :: vector di default.

Nei commenti, l'utente @NathanOliver fornisce anche un altro buon blog, che ha misurazioni più concrete. https://baptiste-wicht.com/posts/2012/12/cpp-benchmark-vector-list-deque.html .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.