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.