Il existe une image bien connue (aide-mémoire) appelée "Choix du conteneur C++". C'est un organigramme pour choisir le meilleur conteneur pour l'utilisation souhaitée.
Quelqu'un sait-il s'il existe déjà une version C++ 11?
Voici le précédent:
Pas que je sache, mais cela peut être fait textuellement, je suppose. En outre, le graphique est légèrement désactivé, car list
n'est pas un bon conteneur en général, et forward_list
. Les deux listes sont des conteneurs très spécialisés pour des applications de niche.
Pour construire un tel graphique, vous avez juste besoin de deux directives simples:
S'inquiéter des performances est généralement inutile au début. Les grandes considérations O n'interviennent vraiment que lorsque vous commencez à manipuler quelques milliers (ou plus) d'articles.
Il existe deux grandes catégories de conteneurs:
find
puis vous pouvez créer plusieurs adaptateurs par-dessus: stack
, queue
, priority_queue
. Je vais laisser les adaptateurs ici, ils sont suffisamment spécialisés pour être reconnaissables.
Question 1: Associative ?
Question 1.1: Commandé ?
unordered_
conteneur, sinon utilisez son homologue ordonné traditionnel.Question 1.2: Clé séparée ?
map
, sinon utilisez un set
Question 1.3: Doublons ?
multi
, sinon ne le faites pas.Exemple:
Supposons que j'ai plusieurs personnes avec un ID unique qui leur est associé et que je souhaite récupérer les données d'une personne à partir de son ID aussi simplement que possible.
Je veux une fonction find
, donc un conteneur associatif
1.1. Je me fiche de l'ordre, donc un unordered_
récipient
1.2. Ma clé (ID) est distincte de la valeur à laquelle elle est associée, donc un map
1.3. L'ID est unique, donc aucun doublon ne doit s'introduire.
La réponse finale est: std::unordered_map<ID, PersonData>
.
Question 2: Mémoire stable ?
list
Question 2.1: Lequel ?
list
; une forward_list
n'est utile que pour une empreinte mémoire moindre.Question 3: Taille dynamique ?
{ ... }
syntaxe), puis utilisez un array
. Il remplace le tableau C traditionnel, mais avec des fonctions pratiques.Question 4: Double ?
deque
, sinon utilisez un vector
.Vous remarquerez que, par défaut, sauf si vous avez besoin d'un conteneur associatif, votre choix sera un vector
. Il s'avère que c'est aussi recommandation de Sutter et Stroustrup .
J'aime la réponse de Matthieu, mais je vais reformuler l'organigramme comme suit:
Par défaut, si vous avez besoin d'un conteneur de trucs, utilisez std::vector
. Ainsi, tout autre conteneur n'est justifié qu'en fournissant une alternative fonctionnelle à std::vector
.
std::vector
Nécessite que son contenu soit constructible, car il doit pouvoir mélanger les éléments autour. Ce n'est pas un lourd fardeau à placer sur le contenu (notez que les constructeurs par défaut ne sont pas pas nécessaires , grâce à emplace
et ainsi de suite) . Cependant, la plupart des autres conteneurs ne nécessitent aucun constructeur particulier (encore une fois, grâce à emplace
). Donc, si vous avez un objet où vous ne pouvez absolument pas implémenter un constructeur de déplacement, alors vous devrez choisir autre chose.
Un std::deque
Serait le remplacement général, ayant de nombreuses propriétés de std::vector
, Mais vous ne pouvez insérer que les deux extrémités du deque. Les inserts au milieu nécessitent un déplacement. Un std::list
N'impose aucune exigence sur son contenu.
std::vector<bool>
N'est ... pas. Eh bien, c'est standard. Mais ce n'est pas un vector
au sens habituel, car les opérations que std::vector
Permet normalement sont interdites. Et très certainement ne contient pas bool
s .
Par conséquent, si vous avez besoin d'un comportement réel de vector
à partir d'un conteneur de bool
s, vous ne l'obtiendrez pas de std::vector<bool>
. Vous devrez donc vous acquitter d'un std::deque<bool>
.
Si vous devez trouver des éléments dans un conteneur et que la balise de recherche ne peut pas être simplement un index, vous devrez peut-être abandonner std::vector
Au profit de set
et map
. Notez le mot clé " may "; un std::vector
trié est parfois une alternative raisonnable. Ou Boost.Container flat_set/map
, qui implémente un std::vector
Trié.
Il en existe maintenant quatre variantes, chacune ayant ses propres besoins.
map
lorsque la balise de recherche n'est pas la même chose que l'élément que vous recherchez lui-même. Sinon, utilisez un set
.unordered
lorsque vous avez beaucoup d'éléments dans le conteneur et que les performances de recherche doivent absolument être O(1)
, plutôt que O(logn)
.multi
si vous avez besoin de plusieurs éléments pour avoir la même balise de recherche.Si vous avez besoin d'un conteneur d'éléments à toujours trier en fonction d'une opération de comparaison particulière, vous pouvez utiliser un set
. Ou un multi_set
Si vous avez besoin de plusieurs éléments pour avoir la même valeur.
Ou vous pouvez utiliser un std::vector
Trié, mais vous devrez le garder trié.
Lorsque les itérateurs et les références sont invalidés, c'est parfois un problème. Si vous avez besoin d'une liste d'éléments, de sorte que vous ayez des itérateurs/pointeurs vers ces éléments à divers autres endroits, l'approche de l'invalidation de std::vector
Peut ne pas être appropriée. Toute opération d'insertion peut entraîner une invalidation, selon la taille et la capacité actuelles.
std::list
Offre une garantie ferme: un itérateur et ses références/pointeurs associés ne sont invalidés que lorsque l'article lui-même est retiré du conteneur. std::forward_list
Est là si la mémoire est un problème grave.
Si c'est une garantie trop forte, std::deque
Offre une garantie plus faible mais utile. L'invalidation résulte des insertions au milieu, mais les insertions en tête ou en queue ne provoquent que l'invalidation des itérateurs , pas des pointeurs/références à des éléments dans le conteneur.
std::vector
Ne fournit qu'une insertion bon marché à la fin (et même alors, cela devient cher si vous soufflez de capacité).
std::list
Est coûteux en termes de performances (chaque élément nouvellement inséré coûte une allocation de mémoire), mais il est cohérent . Il offre également la possibilité parfois indispensable de mélanger les articles pour pratiquement aucun coût de performance, ainsi que d'échanger des articles avec d'autres conteneurs std::list
Du même type sans perte de performances. Si vous devez souvent mélanger , utilisez std::list
.
std::deque
Permet une insertion/retrait à temps constant au niveau de la tête et de la queue, mais l'insertion au milieu peut être assez coûteuse. Donc, si vous devez ajouter/supprimer des éléments à l'avant comme à l'arrière, std::deque
Pourrait être ce dont vous avez besoin.
Il convient de noter que, grâce à la sémantique de déplacement, les performances d'insertion de std::vector
Peuvent ne pas être aussi mauvaises qu'auparavant. Certaines implémentations implémentaient une forme de copie d'éléments sémantique basée sur le déplacement (la soi-disant "swaptimisation"), mais maintenant que le déplacement fait partie du langage, il est mandaté par la norme.
std::array
Est un bon conteneur si vous voulez le moins d'allocations dynamiques possibles. C'est juste un wrapper autour d'un tableau C; cela signifie que sa taille doit être connue au moment de la compilation . Si vous pouvez vivre avec cela, utilisez std::array
.
Cela étant dit, utiliser std::vector
Et reserve
ing une taille fonctionnerait tout aussi bien pour un std::vector
Borné. De cette façon, la taille réelle peut varier et vous n'obtenez qu'une seule allocation de mémoire (sauf si vous gaspillez la capacité).
Voici la version C++ 11 de l'organigramme ci-dessus. [initialement publié sans attribution à son auteur d'origine, Mikael Persson ]
Voici un tour rapide, bien qu'il ait probablement besoin de travail
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
Vous remarquerez peut-être que cela diffère énormément de la version C++ 03, principalement en raison du fait que je n'aime vraiment pas les nœuds liés. Les conteneurs de nœuds liés peuvent généralement être battus en performances par un conteneur non lié, sauf dans quelques rares situations. Si vous ne savez pas quelles sont ces situations et avez accès à Boost, n'utilisez pas de conteneurs de nœuds liés. (std :: list, std :: slist, std :: map, std :: multimap, std :: set, std :: multiset). Cette liste se concentre principalement sur les petits et moyens conteneurs, car (A) c'est 99,99% de ce que nous traitons dans le code, et (B) Un grand nombre d'éléments ont besoin d'algorithmes personnalisés, pas de conteneurs différents.