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.
Est-ce que quelqu'un sait s'il existe déjà une version C ++ 11 de celui-ci?
C'est le précédent:
Réponses:
Pas que je sache, mais cela peut être fait textuellement, je suppose. En outre, le graphique est légèrement décalé, car ce
list
n'est pas un si bon conteneur en général, et ni l'un ni l'autreforward_list
. Les deux listes sont des conteneurs très spécialisés pour des applications de niche.Pour créer un tel graphique, vous avez juste besoin de deux directives simples:
Se soucier des performances est généralement inutile au début. Les grandes considérations O ne prennent vraiment effet que lorsque vous commencez à gérer quelques milliers (ou plus) d'éléments.
Il existe deux grandes catégories de conteneurs:
find
opérationet vous pouvez construire plusieurs adaptateurs sur eux:
stack
,queue
,priority_queue
. Je vais laisser les adaptateurs ici, ils sont suffisamment spécialisés pour être reconnaissables.Question 1: Associatif ?
Question 1.1: commandé ?
unordered_
conteneur, sinon utilisez son équivalent commandé traditionnel.Question 1.2: Clé séparée ?
map
, sinon utilisez unset
Question 1.3: Duplicata ?
multi
, sinon ne le faites pas.Exemple:
Supposons que plusieurs personnes soient associées à un identifiant unique et que je souhaite récupérer les données d'une personne à partir de son identifiant le plus simplement possible.
Je veux une
find
fonction, donc un conteneur associatif1.1. Je me fiche de la commande, donc d'un
unordered_
conteneur1.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'y glisser.
La réponse finale est:
std::unordered_map<ID, PersonData>
.Question 2: Mémoire stable ?
list
Question 2.1: Lequel ?
list
; aforward_list
n'est utile que pour une moindre empreinte mémoire.Question 3: dimensionné dynamiquement ?
{ ... }
syntaxe), alors utilisez unarray
. Il remplace le C-array traditionnel, mais avec des fonctions pratiques.Question 4: Double-terminé ?
deque
, sinon utilisez avector
.Vous noterez 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 la recommandation de Sutter et Stroustrup .la source
array
ne nécessite pas de type constructible par défaut; 2) choisir lesmulti
s ne consiste pas tant à autoriser les doublons, mais plutôt à savoir si leur conservation est importante (vous pouvez placer les doublons dans des non-multi
conteneurs, il se trouve qu'un seul est conservé).map.find(key)
est beaucoup plus acceptable questd::find(map.begin(), map.end(), [&](decltype(map.front()) p) { return p.first < key; }));
si, donc il est important, sémantiquement, que cefind
soit une fonction membre plutôt que celle de<algorithm>
. Quant à O (1) vs O (log n), cela n'affecte pas la sémantique; Je supprimerai le "efficacement" de l'exemple et le remplacerai par "facilement".deque
avoir cette propriété aussi?deque
les éléments ne sont stables que si vous poussez / pop à l'une ou l'autre extrémité; si vous commencez à insérer / effacer au milieu, jusqu'à N / 2 éléments sont mélangés pour combler le vide créé.J'aime la réponse de Matthieu, mais je vais reformuler l'organigramme comme suit:
Quand NE PAS utiliser std :: vector
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 fonctionnalité alternative àstd::vector
.Constructeurs
std::vector
exige que son contenu soit constructible par déplacement, car il doit être capable de mélanger les éléments. Ce n'est pas un fardeau terrible à placer sur le contenu (notez que les constructeurs par défaut ne sont 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 pour lequel vous ne pouvez absolument pas implémenter un constructeur de déplacement, vous devrez choisir autre chose.Un
std::deque
serait le remplacement général, ayant de nombreuses propriétés destd::vector
, mais vous ne pouvez insérer qu'aux deux extrémités du deque. Les inserts au milieu nécessitent un déplacement. Astd::list
n'impose aucune exigence sur son contenu.Besoin de booléens
std::vector<bool>
n'est pas. Eh bien, c'est standard. Mais ce n'est pas unvector
dans le sens habituel, car les opérations qui lestd::vector
permettent normalement sont interdites. Et il ne contientbool
certainement pas l' art .Par conséquent, si vous avez besoin d'un
vector
comportement réel d'un conteneur debool
s, vous n'allez pas l'obtenirstd::vector<bool>
. Vous devrez donc respecter unstd::deque<bool>
.Recherche
Si vous avez besoin de 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
en faveur deset
etmap
. Notez le mot clé « peut »; un tristd::vector
est parfois une alternative raisonnable. Ou Boost.Container'sflat_set/map
, qui implémente un fichier triéstd::vector
.Il existe désormais quatre variantes de celles-ci, 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. Sinon, utilisez unset
.unordered
lorsque vous avez beaucoup d'éléments dans le conteneur et que les performances de recherche doivent absolument l'êtreO(1)
plutôt queO(logn)
.multi
si vous avez besoin que plusieurs éléments aient la même balise de recherche.Commande
Si vous avez besoin qu'un conteneur d'éléments soit toujours trié en fonction d'une opération de comparaison particulière, vous pouvez utiliser un fichier
set
. Oumulti_set
si vous avez besoin de plusieurs éléments pour avoir la même valeur.Ou vous pouvez utiliser un trié
std::vector
, mais vous devrez le garder trié.Stabilité
L'invalidation des itérateurs et des références pose parfois problème. Si vous avez besoin d'une liste d'éléments, de telle sorte que vous ayez des itérateurs / pointeurs vers ces éléments à divers autres endroits, alors
std::vector
l'approche de l'invalidation 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'élément lui-même est retiré du conteneur.std::forward_list
est là si la mémoire est un problème sérieux.Si c'est une garantie trop forte,
std::deque
offre une garantie plus faible mais utile. L'invalidation résulte d'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 aux éléments dans le conteneur.Performances d'insertion
std::vector
ne fournit qu'une insertion bon marché à la fin (et même dans ce cas, cela devient coûteux si vous soufflez de capacité).std::list
est cher 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 capacité parfois indispensable de mélanger des articles pour pratiquement aucun coût de performance, ainsi que d'échanger des articles avec d'autresstd::list
conteneurs du même type sans perte de performance. Si vous devez beaucoup mélanger les choses , utilisezstd::list
.std::deque
fournit une insertion / retrait en temps constant à la tête et à la queue, mais l'insertion au milieu peut être assez coûteuse. Donc, si vous avez besoin d'ajouter / supprimer des éléments de l'avant comme de l'arrière, c'eststd::deque
peut-être ce dont vous avez besoin.Il convient de noter que, grâce à la sémantique de déplacement,
std::vector
les performances d'insertion peuvent ne pas être aussi mauvaises qu'auparavant. Certaines implémentations ont implémenté une forme de copie d'élément basée sur la sémantique (la soi-disant "swaptimisation"), mais maintenant que le déplacement fait partie du langage, il est mandaté par le standard.Aucune allocation dynamique
std::array
est un bon conteneur si vous voulez le moins d'allocations dynamiques possibles. C'est juste un wrapper autour d'un C-array; cela signifie que sa taille doit être connue au moment de la compilation . Si vous pouvez vivre avec cela, utilisezstd::array
.Cela étant dit, utiliser
std::vector
et utiliserreserve
une taille fonctionnerait tout aussi bien pour un bornéstd::vector
. De cette façon, la taille réelle peut varier et vous n'obtenez qu'une seule allocation de mémoire (sauf si vous faites exploser la capacité).la source
std::sort
, il y a aussistd::inplace_merge
ce qui est intéressant pour placer facilement de nouveaux éléments (plutôt qu'un appelstd::lower_bound
+std::vector::insert
). Agréable à apprendreflat_set
etflat_map
!vector<bool>
estvector<char>
.std::allocator<T>
cela ne prend pas en charge cet alignement (et je ne sais pas pourquoi il ne le ferait pas), vous pouvez toujours utiliser votre propre allocateur personnalisé.std::vector::resize
a une surcharge qui ne prend pas de valeur (il prend juste la nouvelle taille; tout nouvel élément sera construit par défaut sur place). Aussi, pourquoi les compilateurs sont-ils incapables d'aligner correctement les paramètres de valeur, même lorsqu'ils sont déclarés avoir cet alignement?bitset
pour bool si vous connaissez la taille à l'avance en.cppreference.com/w/cpp/utility/bitsetVoici la version C ++ 11 de l'organigramme ci-dessus. [publié à l'origine sans attribution à son auteur original, Mikael Persson ]
la source
Voici un tour rapide, même s'il a probablement besoin de travail
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 que vous 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 conteneurs petits et moyens, car (A) c'est 99,99% de ce que nous traitons dans le code, et (B) Un grand nombre d'éléments nécessitent des algorithmes personnalisés, pas des conteneurs différents.
la source