La particularité des itérateurs est qu'ils fournissent la colle entre algorithmes et conteneurs . Pour le code générique, il est recommandé d'utiliser une combinaison d'algorithmes STL (par ex. find
, sort
, remove
, copy
) etc. qui effectue le calcul que vous avez en tête sur votre structure de données ( vector
, list
, map
etc.), et de fournir cet algorithme avec des itérateurs dans votre conteneur.
Votre exemple particulier pourrait être écrit comme une combinaison des éléments suivants for_each
et l'algorithme vector
(voir option 3) ci-dessous), mais ce n'est qu'une des quatre manières distinctes d'itérer sur un std::vector :
1) itération basée sur l'index
for (std::size_t i = 0; i != v.size(); ++i) {
// access element as v[i]
// any code including continue, break, return
}
Avantages : familier à toute personne connaissant le code de style C, peut boucler en utilisant différentes strides (par ex. i += 2
).
Inconvénients : uniquement pour les conteneurs à accès aléatoire séquentiel ( vector
, array
, deque
), ne fonctionne pas pour list
, forward_list
ou les conteneurs associatifs. De plus, le contrôle des boucles est un peu verbeux (init, check, increment). Les gens doivent être conscients de l'indexation basée sur 0 en C++.
2) l'itération basée sur l'itérateur
for (auto it = v.begin(); it != v.end(); ++it) {
// if the current index is needed:
auto i = std::distance(v.begin(), it);
// access element as *it
// any code including continue, break, return
}
Avantages : plus générique, fonctionne pour tous les conteneurs (même les nouveaux conteneurs associatifs non ordonnés, peut également utiliser des strides différents (par ex. std::advance(it, 2)
) ;
Inconvénients Il faut un travail supplémentaire pour obtenir l'indice de l'élément courant (cela pourrait être O(N) pour une liste ou une forward_list). Encore une fois, le contrôle de la boucle est un peu verbeux (init, check, increment).
3) Algorithme STL for_each + lambda
std::for_each(v.begin(), v.end(), [](T const& elem) {
// if the current index is needed:
auto i = &elem - &v[0];
// cannot continue, break or return out of the loop
});
Avantages Même chose qu'en 2) plus une petite réduction du contrôle de la boucle (pas de vérification et d'incrémentation), cela peut réduire considérablement votre taux d'erreurs (init erroné, vérification ou incrémentation, erreurs de type off-by-one).
Inconvénients : même chose que l'itérateur-boucle explicite, plus des possibilités restreintes pour le contrôle du flux dans la boucle (on ne peut pas utiliser continue, break ou return) et aucune option pour des strides différents (à moins d'utiliser un adaptateur d'itérateur qui surcharge operator++
).
4) boucle range-for
for (auto& elem: v) {
// if the current index is needed:
auto i = &elem - &v[0];
// any code including continue, break, return
}
Avantages : contrôle de boucle très compact, accès direct à l'élément de courant.
Inconvénients : déclaration supplémentaire pour obtenir l'index. Impossible d'utiliser des strides différents.
Que faut-il utiliser ?
Pour votre exemple particulier d'itération sur std::vector
Si vous avez vraiment besoin de l'index (par exemple, pour accéder à l'élément précédent ou suivant, imprimer/enregistrer l'index à l'intérieur de la boucle, etc.) ou si vous avez besoin d'un stride différent de 1, alors je choisirais la boucle explicitement indexée, sinon je choisirais la boucle range-for.
Pour les algorithmes génériques sur des conteneurs génériques, j'opterais pour la boucle d'itérateur explicite, à moins que le code ne contienne aucun contrôle de flux à l'intérieur de la boucle et qu'il ait besoin de stride 1, auquel cas j'opterais pour la STL for_each
+ un lambda.