Há uma imagem bem conhecida (folha de dicas) chamada "Escolha do container C ++". É um fluxograma para escolher o melhor recipiente para o uso desejado.
Alguém sabe se já existe uma versão em C ++ 11?
Este é o anterior:
Há uma imagem bem conhecida (folha de dicas) chamada "Escolha do container C ++". É um fluxograma para escolher o melhor recipiente para o uso desejado.
Alguém sabe se já existe uma versão em C ++ 11?
Este é o anterior:
Respostas:
Não que eu saiba, no entanto, isso pode ser feito textualmente, eu acho. Além disso, o gráfico está um pouco desligado, porque list
geralmente não é um contêiner tão bom nem o é forward_list
. Ambas as listas são contêineres muito especializados para aplicações de nicho.
Para criar esse gráfico, você só precisa de duas diretrizes simples:
Preocupar-se com o desempenho é geralmente inútil no início. As grandes considerações de O realmente surgem quando você começa a manusear alguns milhares (ou mais) de itens.
Existem duas grandes categorias de contêineres:
find
operaçãoe então você pode construir vários adaptadores em cima deles: stack
, queue
, priority_queue
. Deixarei os adaptadores aqui fora, eles são suficientemente especializados para serem reconhecidos.
Pergunta 1: associativo ?
Pergunta 1.1: Encomenda ?
unordered_
contêiner; caso contrário, use sua contraparte tradicional solicitada.Pergunta 1.2: Chave separada ?
map
, caso contrário, use umset
Pergunta 1.3: Duplicatas ?
multi
, caso contrário não.Exemplo:
Suponha que eu tenha várias pessoas com um ID exclusivo associado a elas e gostaria de recuperar os dados de uma pessoa a partir dele, da maneira mais simples possível.
Eu quero uma find
função, portanto, um contêiner associativo
1.1 Eu não poderia me importar menos com a ordem, portanto, um unordered_
contêiner
1.2 Minha chave (ID) é separada do valor ao qual está associada, portanto, ummap
1.3 O ID é único, portanto, nenhuma duplicata deve aparecer.
A resposta final é: std::unordered_map<ID, PersonData>
.
Pergunta 2: Memória estável ?
list
Pergunta 2.1: Qual ?
list
; a forward_list
é útil apenas para menos espaço ocupado na memória.Pergunta 3: Dimensionado dinamicamente ?
{ ... }
sintaxe), use um array
. Ele substitui o C-array tradicional, mas por funções convenientes.Pergunta 4: com duas pontas ?
deque
; caso contrário, use a vector
.Você observará que, por padrão, a menos que precise de um contêiner associativo, sua escolha será a vector
. Acontece que também é a recomendação de Sutter e Stroustrup .
array
não requer um tipo construtível padrão; 2) escolher multi
es não é tanto sobre duplicidades serem permitidas, mas mais sobre se mantê- las é importante (você pode colocar duplicatas em não multi
contêineres, acontece que apenas uma é mantida).
map.find(key)
é muito mais palatável do que no std::find(map.begin(), map.end(), [&](decltype(map.front()) p) { return p.first < key; }));
entanto, portanto, é importante, semanticamente, que find
seja uma função de membro e não a função de <algorithm>
. Quanto a O (1) vs O (log n), não afeta a semântica; Vou remover o "eficientemente" do exemplo e substituí-lo por "facilmente".
deque
tivesse essa propriedade também?
deque
os elementos são estáveis somente se você pressionar / pop em cada extremidade; se você começar a inserir / apagar no meio, até N / 2 elementos serão embaralhados para preencher a lacuna criada.
Gosto da resposta de Matthieu, mas vou reafirmar o fluxograma da seguinte maneira:
Por padrão, se você precisar de um contêiner de material, use std::vector
. Assim, todos os outros contêineres são justificados apenas fornecendo alguma funcionalidade alternativa a std::vector
.
std::vector
requer que seu conteúdo seja construtível para movimentação, pois precisa ser capaz de embaralhar os itens. Isso não é um fardo terrível para colocar no conteúdo (observe que construtores padrão não são necessários , graças a emplace
outras coisas). No entanto, a maioria dos outros contêineres não requer nenhum construtor em particular (novamente, graças a emplace
). Portanto, se você tem um objeto em que absolutamente não pode implementar um construtor de movimento, precisará escolher outra coisa.
A std::deque
seria a substituição geral, com muitas das propriedades de std::vector
, mas você só pode inserir nas duas extremidades do deque. Inserções no meio exigem movimento. A std::list
não exige requisitos em seu conteúdo.
std::vector<bool>
não é. Bem, é padrão. Mas não é um vector
no sentido usual, pois as operações que std::vector
normalmente permitem são proibidas. E certamente não contém bool
s .
Portanto, se você precisar de um vector
comportamento real de um contêiner de bool
s, não será possível obtê-lo std::vector<bool>
. Então você terá que fazer o devido com um std::deque<bool>
.
Se você precisar encontrar elementos em um contêiner, e a tag de pesquisa não puder ser apenas um índice, poderá ser necessário abandonar a std::vector
favor de set
e map
. Observe a palavra-chave " may "; um ordenado std::vector
às vezes é uma alternativa razoável. Ou Boost.Container's flat_set/map
, que implementa uma classificação std::vector
.
Agora existem quatro variações delas, cada uma com suas próprias necessidades.
map
quando a tag de pesquisa não for a mesma coisa que o item que você está procurando. Caso contrário, use a set
.unordered
quando você tiver muitos itens no contêiner e o desempenho da pesquisa for absolutamente necessário O(1)
, e não O(logn)
.multi
se você precisar de vários itens para ter a mesma tag de pesquisa.Se você precisar que um contêiner de itens seja sempre classificado com base em uma operação de comparação específica, poderá usar a set
. Ou a, multi_set
se você precisar de vários itens para ter o mesmo valor.
Ou você pode usar uma classificação std::vector
, mas terá que mantê-la classificada.
Quando iteradores e referências são invalidados, às vezes é uma preocupação. Se você precisar de uma lista de itens, de modo que tenha iteradores / ponteiros para esses itens em vários outros lugares, std::vector
a abordagem da invalidação pode não ser apropriada. Qualquer operação de inserção pode causar invalidação, dependendo do tamanho e capacidade atuais.
std::list
oferece uma garantia firme: um iterador e suas referências / indicadores associados só são invalidados quando o próprio item é removido do contêiner. std::forward_list
existe se a memória é uma preocupação séria.
Se essa é uma garantia muito forte, std::deque
oferece uma garantia mais fraca, mas útil. A invalidação resulta de inserções no meio, mas inserções na cabeça ou na cauda causam apenas invalidação de iteradores , não ponteiros / referências a itens no contêiner.
std::vector
fornece apenas inserção barata no final (e mesmo assim, fica caro se você aumentar a capacidade).
std::list
é caro em termos de desempenho (cada item recém-inserido custa uma alocação de memória), mas é consistente . Ele também oferece a capacidade ocasionalmente indispensável de embaralhar itens praticamente sem custo de desempenho, além de trocar itens com outros std::list
contêineres do mesmo tipo sem perda de desempenho. Se você precisar embaralhar bastante as coisas , use std::list
.
std::deque
fornece inserção / remoção em tempo constante na cabeça e cauda, mas a inserção no meio pode ser bastante cara. Portanto, se você precisar adicionar / remover itens da parte frontal e traseira, std::deque
pode ser o que você precisa.
Note-se que, graças à semântica da movimentação, std::vector
o desempenho da inserção pode não ser tão ruim quanto costumava ser. Algumas implementações implementaram uma forma de cópia de itens baseados em semântica de movimentação (a chamada "swaptimization"), mas agora que a movimentação faz parte do idioma, é exigida pelo padrão.
std::array
é um contêiner fino se você deseja o menor número possível de alocações dinâmicas. É apenas um invólucro em torno de uma matriz C; isso significa que seu tamanho deve ser conhecido em tempo de compilação . Se você pode viver com isso, use std::array
.
Dito isto, o uso std::vector
e reserve
ing de um tamanho funcionariam tão bem para um limite std::vector
. Dessa forma, o tamanho real pode variar e você recebe apenas uma alocação de memória (a menos que reduza a capacidade).
std::sort
, também std::inplace_merge
é interessante colocar facilmente novos elementos (em vez de uma chamada std::lower_bound
+ std::vector::insert
). É bom aprender sobre flat_set
e flat_map
!
vector<bool>
é vector<char>
.
std::allocator<T>
não suportar esse alinhamento (e não sei por que não seria), você sempre poderá usar seu próprio alocador personalizado.
std::vector::resize
tem uma sobrecarga que não leva um valor (apenas assume o novo tamanho; quaisquer novos elementos serão construídos no local por padrão). Além disso, por que os compiladores não conseguem alinhar corretamente os parâmetros de valor, mesmo quando são declarados com esse alinhamento?
bitset
para bool se você sabe o tamanho de antecedência en.cppreference.com/w/cpp/utility/bitset
Aqui está a versão C ++ 11 do fluxograma acima. [publicado originalmente sem atribuição ao autor original, Mikael Persson ]
Aqui está uma rápida rotação, embora provavelmente precise de trabalho
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
Você pode notar que isso difere bastante da versão C ++ 03, principalmente devido ao fato de eu realmente não gostar de nós vinculados. Os contêineres do nó vinculado geralmente podem ser superados no desempenho por um contêiner não vinculado, exceto em algumas situações raras. Se você não souber quais são essas situações e tiver acesso a melhorias, não use contêineres de nós vinculados. (std :: list, std :: slist, std :: map, std :: multimap, std :: set, std :: multiset). Essa lista se concentra principalmente em contêineres pequenos e médios, porque (A) é 99,99% do que lidamos com código e (B) Um grande número de elementos precisa de algoritmos personalizados, não de contêineres diferentes.