Question

Prenez les deux lignes de code suivantes:

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

Et ceci:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

On me dit que la deuxième façon est préférable. Pourquoi est-ce exactement cela?

Était-ce utile?

La solution

La première forme n'est efficace que si vector.size () est une opération rapide. Ceci est vrai pour les vecteurs, mais pas pour les listes, par exemple. Aussi, que comptez-vous faire dans le corps de la boucle? Si vous envisagez d’accéder aux éléments comme dans

T elem = some_vector[i];

alors vous supposez que le conteneur est operator[](std::size_t) défini. Encore une fois, cela est vrai pour le vecteur mais pas pour les autres conteneurs.

L'utilisation d'itérateurs vous rapproche de l'indépendance des conteneurs. Vous ne faites pas d'hypothèses sur la capacité d'accès aléatoire ou sur une size() opération rapide, mais sur le fait que le conteneur dispose de capacités d'itérateur.

Vous pouvez améliorer davantage votre code en utilisant des algorithmes standard. Selon ce que vous essayez d’atteindre, vous pouvez choisir d’utiliser std::for_each(), std::transform() et ainsi de suite. En utilisant un algorithme standard plutôt qu'une boucle explicite, vous évitez de réinventer la roue. Votre code sera probablement plus efficace (à condition de choisir le bon algorithme), correct et réutilisable.

Autres conseils

parce que vous n'attachez pas votre code à l'implémentation particulière de la liste some_vector. si vous utilisez des indices de tableau, il doit s'agir d'une forme de tableau; si vous utilisez des itérateurs, vous pouvez utiliser ce code pour toute implémentation de liste.

Cela fait partie du processus d’endoctrinement C ++ moderne. Les itérateurs sont le seul moyen d'itérer la plupart des conteneurs, vous pouvez donc les utiliser, même avec des vecteurs, pour vous mettre dans la bonne mentalité. Sérieusement, c'est la seule raison pour laquelle je le fais - je ne pense pas avoir jamais remplacé un vecteur par un type de conteneur différent.


Wow, c'est toujours en baisse après trois semaines. Je suppose que ça ne paie pas d’être un peu ironique.

Je pense que l'index de tableau est plus lisible. Cela correspond à la syntaxe utilisée dans d'autres langages et à la syntaxe utilisée pour les tableaux C à l'ancienne. C'est aussi moins verbeux. Si votre compilateur est bon, l’efficacité devrait faire l’objet d’un lavage, et il n’ya guère de cas où cela compte de toute façon.

Malgré cela, je me trouve toujours à utiliser des itérateurs fréquemment avec des vecteurs. Je pense que l'itérateur est un concept important, je le promeut dès que je peux.

Imagine some_vector est implémenté avec une liste chaînée. Ensuite, demander un élément à la i-ème place nécessite que i des opérations soient effectuées pour parcourir la liste des nœuds. Maintenant, si vous utilisez iterator, en général, il fera de son mieux pour être aussi efficace que possible (dans le cas d’une liste chaînée, il maintiendra un pointeur sur le nœud actuel et le fera avancer à chaque itération, nécessitant seulement opération unique).

Donc, il fournit deux choses:

  • Abstraction d'utilisation: vous voulez juste réitérer certains éléments, vous ne vous souciez pas de savoir comment le faire
  • Performances

Je vais être le défenseur des démons ici, et non pas recommander des itérateurs. La raison principale en est que tout le code source sur lequel j'ai travaillé, du développement d'applications bureautiques au développement de jeux, n'a pas besoin d'utiliser des itérateurs. Tout le temps qu’ils n’ont pas été nécessaires et deuxièmement, les hypothèses cachées, le désordre de code et les cauchemars de débogage que vous obtenez avec les itérateurs en font un excellent exemple à ne pas utiliser dans des applications nécessitant de la vitesse.

Même du point de vue de la maintenance, ils sont en désordre. Ce n'est pas à cause d'eux mais à cause de tous les pseudonymes qui se produisent dans les coulisses. Comment puis-je savoir que vous n'avez pas mis en œuvre votre propre liste de vecteurs ou de tableaux virtuels qui fait quelque chose de complètement différent des normes. Est-ce que je sais quel type est actuellement en cours d'exécution? Avez-vous surchargé un opérateur? Je n'ai pas eu le temps de vérifier tout votre code source. Hell, est-ce que je sais même quelle version de la STL vous utilisez?

Le problème suivant que vous avez avec les itérateurs est l’abstraction qui fuit, bien que de nombreux sites Web discutent de cela en détail avec eux.

Désolé, je n'ai pas vu et je n'ai toujours pas vu de point en itérateurs. S'ils font abstraction de la liste ou du vecteur, alors qu'en fait, vous devez déjà savoir quel vecteur ou répertorier vos relations si vous ne le faites pas, vous vous apprêtez à vous préparer à de superbes sessions de débogage à l'avenir.

Vous pouvez utiliser un itérateur si vous souhaitez ajouter / supprimer des éléments au vecteur tout en effectuant une itération dessus.

some_iterator = some_vector.begin(); 
while (some_iterator != some_vector.end())
{
    if (/* some condition */)
    {
        some_iterator = some_vector.erase(some_iterator);
        // some_iterator now positioned at the element after the deleted element
    }
    else
    {
        if (/* some other condition */)
        {
            some_iterator = some_vector.insert(some_iterator, some_new_value);
            // some_iterator now positioned at new element
        }
        ++some_iterator;
    }
}

Si vous utilisiez des index, vous auriez à mélanger les éléments dans le tableau pour gérer les insertions et les suppressions.

Séparation des problèmes

Il est très agréable de séparer le code d’itération de la préoccupation principale de la boucle. C'est presque une décision de conception.

En effet, itérer par index vous lie à la mise en oeuvre du conteneur. Demander au conteneur un itérateur de début et de fin active le code de boucle à utiliser avec d'autres types de conteneur.

De la même manière, vous DITES à la collection ce qu'il faut faire, au lieu de DEMANDER quelque chose à propos de ses composants internes

Le standard 0x va introduire des fermetures, ce qui rendra cette approche beaucoup plus facile à utiliser - jetez un coup d'œil au pouvoir expressif de, par exemple. Ruby std::for_each ...

Performances

Mais il est peut-être un problème bien négligé: l'utilisation de l'approche [1..6].each { |i| print i; } offre la possibilité de paralléliser l'itération - le intel threading blocks peut répartir le bloc de code sur le nombre de processeurs du système!

Remarque: après avoir découvert la for_each bibliothèque, et plus particulièrement algorithms, j’ai passé deux ou trois mois à écrire des structures d’opérateur 'helper' ridiculement petites qui rendraient fous vos collègues développeurs. Après cette période, je suis retourné à une approche pragmatique: les corps de petite boucle ne méritent plus de foreach:

Une référence à lire impérativement sur les itérateurs est le livre & "; STL étendu &"; .

Le GoF a un tout petit paragraphe à la fin du motif Iterator, qui parle de cette sorte d’itération; cela s'appelle un «itérateur interne». Consultez ici également.

Parce que c'est plus orienté objet. si vous effectuez une itération avec un index, vous supposez:

a) que ces objets sont ordonnés
b) que ces objets peuvent être obtenus par un index
c) que l'incrément d'index va frapper chaque article
d) que cet index commence à zéro

Avec un itérateur, vous dites & "donnez-moi tout pour que je puisse travailler avec cela &"; sans savoir quelle est l'implémentation sous-jacente. (En Java, certaines collections ne sont pas accessibles via un index)

En outre, avec un itérateur, nul besoin de vous inquiéter de sortir des limites du tableau.

Une autre bonne chose à propos des itérateurs est qu’ils vous permettent mieux d’exprimer (et d’appliquer) votre préférence const. Cet exemple garantit que vous ne modifierez pas le vecteur au milieu de votre boucle:


for(std::vector<Foo>::const_iterator pos=foos.begin(); pos != foos.end(); ++pos)
{
    // Foo & foo = *pos; // this won't compile
    const Foo & foo = *pos; // this will compile
}

Mis à part toutes les autres excellentes réponses ... int peut ne pas être assez grand pour votre vecteur. Si vous souhaitez utiliser l'indexation, utilisez plutôt le size_type pour votre conteneur:

for (std::vector<Foo>::size_type i = 0; i < myvector.size(); ++i)
{
    Foo& this_foo = myvector[i];
    // Do stuff with this_foo
}

Je devrais probablement préciser que vous pouvez également appeler

std::for_each(some_vector.begin(), some_vector.end(), &do_stuff);

Les itérateurs STL sont généralement présents afin que les algorithmes STL tels que le tri puissent être indépendants du conteneur.

Si vous souhaitez simplement parcourir toutes les entrées d’un vecteur, utilisez simplement le style de boucle d’index.

Il est moins difficile de taper et plus facile à analyser pour la plupart des humains. Ce serait bien si C ++ avait une simple boucle foreach sans aller trop loin avec le template magique.

for( size_t i = 0; i < some_vector.size(); ++i )
{
   T& rT = some_vector[i];
   // now do something with rT
}
'

Je ne pense pas que cela fasse une grande différence pour un vecteur. Je préfère utiliser moi-même un index, car j'estime qu'il est plus lisible et que vous pouvez effectuer un accès aléatoire, comme sauter 6 éléments ou sauter si nécessaire.

J'aime aussi faire référence à l'élément situé dans la boucle de cette manière, de sorte qu'il n'y ait pas beaucoup de crochets autour de l'endroit:

for(size_t i = 0; i < myvector.size(); i++)
{
    MyClass &item = myvector[i];

    // Do stuff to "item".
}

L'utilisation d'un itérateur peut s'avérer utile si vous pensez que vous devrez peut-être remplacer le vecteur par une liste à un moment ultérieur. Il est également plus élégant pour les fous de la STL, mais je ne vois aucune autre raison.

Le deuxième formulaire représente ce que vous faites plus précisément. Dans votre exemple, la valeur de i vous importe peu, tout ce que vous voulez, c'est l'élément suivant de l'itérateur.

Après en avoir appris un peu plus sur le sujet de cette réponse, je me suis rendu compte que c’était un peu trop simpliste. La différence entre cette boucle:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

Et cette boucle:

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

Est assez minime. En fait, la syntaxe de faire des boucles de cette façon semble grandir sur moi:

while (it != end){
    //do stuff
    ++it;
}

Les itérateurs déverrouillent certaines fonctionnalités déclaratives assez puissantes et, une fois combinés à la bibliothèque d'algorithmes STL, vous pouvez réaliser des tâches vraiment intéressantes en dehors du domaine de la gestion des index par tableaux.

L'indexation nécessite une mul opération supplémentaire. Par exemple, pour vector<int> v, le compilateur convertit v[i] en &v + sizeof(int) * i.

Pendant l'itération, vous n'avez pas besoin de connaître le nombre d'éléments à traiter. Vous avez juste besoin de cet élément et les itérateurs font de très bonnes choses.

Personne n'a encore mentionné que l'un des avantages des index est qu'ils ne sont pas invalidés lorsque vous ajoutez un conteneur contigu tel que std::vector, vous pouvez donc ajouter des éléments au conteneur lors de l'itération.

Cela est également possible avec les itérateurs, mais vous devez appeler reserve() et par conséquent connaître le nombre d'éléments à ajouter.

Plusieurs points positifs déjà. J'ai quelques commentaires supplémentaires:

  1. En supposant que nous parlions de la bibliothèque standard C ++, " vecteur " implique un conteneur à accès aléatoire ayant les garanties du tableau C (accès aléatoire, disposition de la mémoire contiguë, etc.). Si vous aviez dit 'un_conteneur', la plupart des réponses ci-dessus auraient été plus précises (indépendance du conteneur, etc.).

  2. Pour éliminer les dépendances sur l'optimisation du compilateur, vous pouvez déplacer some_vector.size () en dehors de la boucle dans le code indexé, comme suit:

    const size_t numElems = some_vector.size();
    for (size_t i = 0; i 
  3. Toujours pré-incrémenter les itérateurs et traiter les post-incréments comme des cas exceptionnels.

for (some_iterator = some_vector.begin (); some_iterator! = some_vector.end (); ++ some_iterator) {// faire des choses}

Donc, en supposant et indexable std::vector<> comme un conteneur, il n’ya aucune bonne raison de en préférer un autre en passant en séquence dans le conteneur. Si vous devez vous référer fréquemment à des index elemnent plus anciens ou plus récents, la version indexée est plus appropriée.

En général, l’utilisation des itérateurs est préférable, car les algorithmes les utilisent et le comportement peut être contrôlé (et implicitement documenté) en modifiant le type de l’itérateur. Les emplacements de tableau peuvent être utilisés à la place des itérateurs, mais la différence syntaxique sera visible.

Je n'utilise pas les itérateurs pour la même raison que je n'aime pas les déclarations -each. Lorsque vous avez plusieurs boucles internes, il est assez difficile de garder une trace des variables globales / membres sans avoir à mémoriser toutes les valeurs locales et les noms d'itérateurs. Ce que je trouve utile, c’est d’utiliser deux ensembles d’index pour différentes occasions:

for(int i=0;i<anims.size();i++)
  for(int j=0;j<bones.size();j++)
  {
     int animIndex = i;
     int boneIndex = j;


     // in relatively short code I use indices i and j
     ... animation_matrices[i][j] ...

     // in long and complicated code I use indices animIndex and boneIndex
     ... animation_matrices[animIndex][boneIndex] ...


  }

Je ne veux même pas abréger des choses comme & "; animation_matrices [i] &"; & "anim_matrix &" nommé-itérateur, par exemple, car vous ne pouvez pas voir clairement de quel tableau provient cette valeur.

  • Si vous aimez être proche du métal / ne vous fiez pas à leurs détails d'implémentation, n'utilisez pas d'itérateurs.
  • Si vous changez régulièrement de type de collection pour un autre au cours du développement, utilisez les itérateurs.
  • Si vous avez des difficultés à vous rappeler comment itérer différents types de collections (plusieurs types provenant de plusieurs sources externes différentes sont peut-être utilisés), utilisez des itérateurs pour unifier les moyens par lesquels vous passez en revue éléments. Ceci s’applique à changer de liste liée avec une liste de tableau.

Vraiment, c’est tout. Ce n'est pas comme si vous alliez gagner en brièveté dans les deux cas en moyenne, et si l'objectif est vraiment d'être bref, vous pouvez toujours vous rabattre sur les macros.

Encore mieux que & "Dire au processeur quoi faire" & "; (impératif) signifie & "Indiquez aux bibliothèques ce que vous voulez &"; (fonctionnel).

Donc, au lieu d'utiliser des boucles, vous devriez apprendre les algorithmes présents dans stl.

Pour l'indépendance du conteneur

J'utilise toujours l'index de tableau, car de nombreuses applications de ce type nécessitent quelque chose comme & "; afficher une image miniature &"; J'ai donc écrit quelque chose comme ceci:

some_vector[0].left=0;
some_vector[0].top =0;<br>

for (int i = 1; i < some_vector.size(); i++)
{

    some_vector[i].left = some_vector[i-1].width +  some_vector[i-1].left;
    if(i % 6 ==0)
    {
        some_vector[i].top = some_vector[i].top.height + some_vector[i].top;
        some_vector[i].left = 0;
    }

}

Les deux implémentations sont correctes, mais je préférerais la boucle "pour". Comme nous avons décidé d’utiliser un vecteur et pas un autre conteneur, l’utilisation d’index serait la meilleure option. L'utilisation d'itérateurs avec des vecteurs perdrait l'avantage même de disposer les objets dans des blocs de mémoire continus, ce qui faciliterait leur accès.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top