Em que cenário eu uso um contêiner STL específico?


185

Estive lendo sobre contêineres STL em meu livro sobre C ++, especificamente a seção sobre o STL e seus contêineres. Agora eu entendo que cada um deles tem suas próprias propriedades específicas e estou quase memorizando todas elas ... Mas o que ainda não entendo é em que cenário cada uma delas é usada.

Qual a explicação? Código de exemplo é muito preferido.


você quer dizer mapa, vectot, conjunto etc?
Thomas Tempelmann

Mesmo olhando para este diagrama não posso dizer qual seria o melhor para usar na minha quastion stackoverflow.com/questions/9329011/...
sergiol

2
@sbi: Removendo a tag Faq do C ++ e adicionando-a à mais recente e inclusive à C ++ 11 Como posso selecionar com eficiência um contêiner de biblioteca padrão no C ++ 11?
Alok Salvar

Respostas:


338

Esta folha de dicas fornece um resumo muito bom dos diferentes contêineres.

Veja o fluxograma na parte inferior como um guia sobre o qual usar em diferentes cenários de uso:

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

Criado por David Moore e licenciado CC BY-SA 3.0


14
Este fluxograma é de ouro, eu gostaria de ter algo parecido com isso em c #
de Bruno

2

3
O ponto de partida deve estar um vectorpouco vazio. stackoverflow.com/questions/10699265/…
eonil

5
Agora você tem unordered_mape unordered_set(e suas múltiplas variantes) que não estão no fluxograma, mas são boas escolhas quando você não se importa com a ordem, mas precisa encontrar elementos por chave. Sua pesquisa geralmente é O (1) em vez de O (log n).
Aidiakapi

2
@ shuttle87 não apenas esse tamanho nunca irá variar, mas o mais importante é que esse tamanho é determinado no momento da compilação e nunca irá variar.
YoungJohn

188

Aqui está um fluxograma inspirado na versão de David Moore (veja acima) que eu criei, que está atualizada (principalmente) com o novo padrão (C ++ 11). Esta é apenas minha opinião pessoal, não é indiscutível, mas achei que poderia ser valioso para esta discussão:

insira a descrição da imagem aqui


4
Você pode disponibilizar o original? É um excelente gráfico. Talvez ficar em um blog ou GitHub?
Kevinarpe

1
Este é um excelente gráfico. Embora alguém possa me explicar o que se entende por "posições persistentes"?
IDDQD

3
@STALKER Posições persistentes significa que, se você tiver um ponteiro ou iterador para um elemento no contêiner, esse ponteiro ou iterador permanecerá válido (e apontando para o mesmo elemento), independentemente do que você adicionar ou remover do contêiner (desde que seja não é o elemento em questão).
Mikael Persson

1
Este é realmente um ótimo gráfico, no entanto, acho que vector (sorted)é um pouco inconsistente com o resto. Não é um tipo diferente de contêiner, apenas o mesmo, std::vectormas classificado. Ainda mais importante, não vejo por que não se poderia usar uma std::setiteração ordenada, se esse é o comportamento padrão da iteração através de um conjunto. Claro, se a resposta está falando sobre o acesso ordenado aos valores do contêiner [], então tudo bem, você só pode fazer isso com um soted std::vector. Mas em ambos os casos, a decisão deve ser tomada logo após a pergunta "ordem é necessária"
RAs

1
@ user2019840 Eu queria restringir o gráfico a contêineres padrão. O que deve aparecer no lugar do "vetor classificado" é "flat_set" (do Boost.Container ) ou equivalente (toda biblioteca principal ou base de código tem um equivalente flat_set, AFAIK). Mas estes não são padrão e uma omissão flagrante do STL. E a razão pela qual você não deseja iterar através de std :: set ou std :: map (pelo menos não com frequência) é que é muito ineficiente fazer isso .
Mikael Persson #

41

Resposta simples: use std::vectorpara tudo, a menos que você tenha um motivo real para fazer o contrário.

Quando você encontrar um caso em que está pensando "Nossa, std::vectornão funciona bem aqui por causa do X", vá com base no X.


1
No entanto .. ter cuidado para não excluir / inserir itens quando a iteração ... uso const_iterator, tanto quanto possível para evitar este ..
vrdhn

11
Hmm ... acho que as pessoas estão usando vetor demais. O motivo é que a caixa "não funciona" não acontecerá facilmente - então as pessoas ficam com o contêiner usado com mais frequência e o usam mal para armazenar listas, filas ... Na minha opinião - que corresponde ao fluxograma - deve-se escolher o contêiner com base no uso pretendido, em vez de aplicar o "um parece servir para todos".
Preto

13
@ Black Point é que o vetor geralmente é mais rápido, mesmo em operações que, em teoria, deveriam funcionar mais devagar.
Bartek Banachewicz

1
O @Vardhan std::remove_ifquase sempre é superior à abordagem "excluir durante a iteração".
Fredoverflow 29/04

1
Alguns benchmarks realmente ajudariam essa discussão a ser menos subjetiva.
Felix D.

11

Veja o STL eficaz de Scott Meyers. É bom em explicar como usar o STL.

Se você deseja armazenar um número determinado / indeterminado de objetos e nunca excluir nenhum, então um vetor é o que você deseja. É a substituição padrão para uma matriz C e funciona como uma, mas não transborda. Você pode definir seu tamanho antecipadamente e também com reserve ().

Se você deseja armazenar um número indeterminado de objetos, mas irá adicioná-los e excluí-los, provavelmente deseja uma lista ... porque é possível excluir um elemento sem mover nenhum dos seguintes elementos - diferente do vetor. Porém, é preciso mais memória que um vetor e você não pode acessar sequencialmente um elemento.

Se você quiser pegar vários elementos e encontrar apenas os valores exclusivos desses elementos, a leitura de todos eles em um conjunto fará isso e os classificará para você também.

Se você possui muitos pares de valores-chave e deseja classificá-los por chave, um mapa é útil ... mas ele contém apenas um valor por chave. Se você precisar de mais de um valor por chave, poderá ter um vetor / lista como seu valor no mapa ou usar um multimap.

Não está no STL, mas está na atualização TR1 para o STL: se você tiver muitos pares de valores-chave, procurará por chave e não se importará com a ordem deles. deseja usar um hash - que é tr1 :: unordered_map. Eu usei com o Visual C ++ 7.1, onde foi chamado stdext :: hash_map. Ele tem uma pesquisa de O (1) em vez de uma pesquisa de O (log n) para o mapa.


Já ouvi algumas histórias sugerindo que a Microsoft hash_mapnão é uma implementação muito boa. Espero que eles tenham se saído melhor unordered_map.
Mark Ransom

3
Das listas - "você não pode acessar sequencialmente um elemento". - Eu acho que você quer dizer que você não pode de acesso aleatório ou índice diretamente a um elemento ....
Tony Delroy

^ Sim, porque o acesso seqüencial é precisamente o que a listfaz. Erro bastante gritante lá.
Sublinhado #

7

Redesenhei o fluxograma para ter 3 propriedades:

  1. Acho que os contêineres da STL são divididos em duas classes principais. Os contêineres básicos e esses aproveitam os contêineres básicos para implementar uma política.
  2. No início, o fluxograma deve dividir o processo de decisão para as principais situações em que devemos decidir e, em seguida, elaborar cada caso.
  3. Alguns contêineres estendidos têm a possibilidade de escolher diferentes contêineres básicos como contêineres internos. O fluxograma deve considerar as situações em que cada um dos contêineres básicos pode ser usado.

O fluxograma: insira a descrição da imagem aqui

Mais informações fornecidas neste link .


5

Um ponto importante apenas brevemente mencionado até agora, é que se você precisar de memória contígua (como uma matriz C dá), então você só pode usar vector, arrayou string.

Use arrayse o tamanho for conhecido no momento da compilação.

Use stringse você precisar trabalhar apenas com tipos de caracteres e precisar de uma sequência, não apenas de um contêiner de uso geral.

Use vectorem todos os outros casos (vector na maioria dos casos, deve ser a escolha padrão do contêiner).

Com os três itens, você pode usar a data()função de membro para obter um ponteiro para o primeiro elemento do contêiner.


3

Tudo depende do que você deseja armazenar e do que deseja fazer com o contêiner. Aqui estão alguns exemplos (não exaustivos) para as classes de contêiner que eu mais uso:

vector: Layout compacto com pouca ou nenhuma sobrecarga de memória por objeto contido. Eficiente para iterar. Anexar, inserir e apagar pode ser caro, principalmente para objetos complexos. Barato para encontrar um objeto contido por índice, por exemplo, myVector [10]. Use onde você teria usado uma matriz em C. Bom, onde você tem muitos objetos simples (por exemplo, int). Não se esqueça de usar reserve()antes de adicionar muitos objetos ao contêiner.

list: Sobrecarga de memória pequena por objeto contido. Eficiente para iterar. Anexar, inserir e apagar são baratos. Use onde você teria usado uma lista vinculada em C.

set(e multiset): sobrecarga significativa de memória por objeto contido. Use onde for necessário descobrir rapidamente se esse contêiner contém um determinado objeto ou mesclar contêineres com eficiência.

map (e multimap ): sobrecarga significativa de memória por objeto contido. Use onde deseja armazenar pares de valores-chave e procure valores por chave rapidamente.

O fluxograma na folha de dicas sugerido por zdan fornece um guia mais completo.


"Pequena sobrecarga de memória por objeto contido" não é verdadeira para a lista. O std :: list é implementado como lista duplamente vinculada e, portanto, mantém 2 ponteiros por objeto armazenado, o que não deve ser negligenciado.
Hanna Khalil

Eu ainda contaria dois ponteiros por objeto armazenado como "pequeno".
Lances

comparado com o que? std :: forward_list é um contêiner que foi sugerido principalmente para ter menos metadados armazenados por objeto (apenas um ponteiro). Enquanto std :: vector contém 0 metadados por objeto. Portanto, 2 ponteiros não são negociáveis ​​em comparação com outros contêineres #
Hanna Khalil

Tudo depende do tamanho dos seus objetos. Eu já disse que o vetor tem um "layout compacto com pouca ou nenhuma sobrecarga de memória por objeto contido". Eu ainda diria que a lista possui uma pequena sobrecarga de memória em comparação com o conjunto e o mapa, e uma sobrecarga de memória um pouco maior que o vetor. Não tenho muita certeza de que ponto você está tentando dizer TBH!
Lances

Todos os contêineres baseados em modo tendem a ter uma sobrecarga significativa devido à alocação dinâmica, que raramente vem de graça. A menos, claro, que você esteja usando um alocador personalizado.
MikeMB 27/08

2

Uma lição que aprendi é: tente envolvê-lo em uma classe, pois alterar o tipo de contêiner em um belo dia pode render grandes surpresas.

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

Não custa muito adiantado e economiza tempo na depuração quando você deseja interromper sempre que alguém faz a operação x nessa estrutura.

Chegando à seleção da estrutura de dados perfeita para um trabalho:

Cada estrutura de dados fornece algumas operações, que podem variar a complexidade do tempo:

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

Você precisa essencialmente adivinhar quais são as operações mais executadas e usar uma estrutura de dados que possua essa operação como O (1).

Simples, não é (-:


5
Não é por isso que usamos iteradores?
Platinum Azure

@PlatinumAzure Até os iteradores devem ser membro typedef. Se você alterar o tipo de contêiner, também precisará alterar todas as definições do iterador ... isso foi corrigido em c ++ 1x!
vrdhn

4
Para o curiosa, esta é a correção em C ++ 11: auto myIterator = whateverCollection.begin(); // <-- immune to changes of container type
Preto

1
Seria typedef Collection<Foo*> CollectionOfFoo;suficiente?
Craig McQueen

5
É muito improvável que você pode simplesmente mudar de idéia mais tarde e simplesmente delegar a um recipiente diferente: Cuidado com a ilusão de código independente de recipiente
fredoverflow


1

Eu respondi isso em outra pergunta que está marcada como dup desta. Mas acho bom consultar alguns bons artigos sobre a decisão de escolher um contêiner padrão.

Como @David Thornley respondeu, std :: vector é o caminho a percorrer se não houver outras necessidades especiais. Este é o conselho dado pelo criador do C ++, Bjarne Stroustrup em um blog de 2014.

Aqui está o link para o artigo https://isocpp.org/blog/2014/06/stroustrup-lists

e citar esse,

E, sim, minha recomendação é usar std :: vector por padrão.

Nos comentários, o usuário @NathanOliver também fornece outro bom blog, que possui medidas mais concretas. https://baptiste-wicht.com/posts/2012/12/cpp-benchmark-vector-list-deque.html .

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.