web-dev-qa-db-fra.com

Que se passe-t-il si vous incrémentez un itérateur qui est égal à l'itérateur de fin d'un conteneur STL

Et si j'incrémente un itérateur de 2 lorsqu'il pointe sur le dernier élément d'un vecteur? Dans cette question demandant comment ajuster l'itérateur à un conteneur STL par 2 éléments, deux approches différentes sont proposées:

  • soit utiliser une forme d'opérateur arithmétique - + = 2 ou ++ deux fois
  • ou utilisez std :: advance ()

J'ai testé les deux avec VC++ 7 pour le cas Edge lorsque l'itérateur pointe sur le dernier élément du conteneur STL ou au-delà:

vector<int> vec;
vec.Push_back( 1 );
vec.Push_back( 2 );

vector<int>::iterator it = vec.begin();
advance( it, 2 );
bool isAtEnd = it == vec.end(); // true
it++; // or advance( it, 1 ); - doesn't matter
isAtEnd = it == vec.end(); //false
it = vec.begin();
advance( it, 3 );
isAtEnd = it == vec.end(); // false

J'ai vu plusieurs fois un conseil pour comparer avec vector :: end () lors de la traversée du vecteur et d'autres conteneurs:

for( vector<int>::iterator it = vec.begin(); it != vec.end(); it++ ) {
    //manipulate the element through the iterator here
}

Évidemment, si l'itérateur est avancé au-delà du dernier élément à l'intérieur de la boucle, la comparaison dans l'instruction for-loop sera évaluée à false et la boucle continuera avec bonheur dans un comportement indéfini.

Dois-je bien comprendre que si jamais j'utilise Advance () ou tout type d'opération d'incrémentation sur un itérateur et que je pointe au-delà de la fin du conteneur, je ne serai pas en mesure de détecter cette situation? Dans l'affirmative, quelle est la meilleure pratique - ne pas utiliser ces avancées?

60
sharptooth

Voici la citation du livre de Nicolai Josuttis:

Notez que Advance () ne vérifie pas si elle traverse la fin () d'une séquence (elle ne peut pas vérifier car les itérateurs en général ne connaissent pas les conteneurs sur lesquels ils opèrent). Ainsi, l'appel de cette fonction peut entraîner un comportement indéfini car l'appel de l'opérateur ++ pour la fin d'une séquence n'est pas défini

En d'autres termes, la responsabilité de maintenir l'itérateur dans la plage incombe entièrement à l'appelant.

61
Naveen

Vous devriez peut-être avoir quelque chose comme ça:

template <typename Itr>
Itr safe_advance(Itr i, Itr end, size_t delta)
{
    while(i != end && delta--)
        i++;
    return i;
}

Vous pouvez surcharger cela lorsque iterator_category<Itr> est random_access_iterator pour faire quelque chose comme ceci:

return (delta > end - i)? end : i + delta;
13
Motti

Vous pouvez utiliser la fonction "distance" entre votre itérateur (it) et l'itérateur de vec.begin () et le comparer avec la taille du vecteur (obtenue par size ()).

Dans ce cas, votre boucle for ressemblerait à ceci:

for (vector<int>::iterator it = vec.begin(); distance(vec.begin(), it) < vec.size(); ++it)
{
     // Possibly advance n times here.
}
7
Kostas

Le code que Marijn suggère est juste légèrement faux (comme l'a souligné curiousguy).

La version correcte de la dernière ligne est:

bool isPastEnd = it >= vec.end();
3
DukeBrymin

Je vous suggère de jeter un œil à Boost.Range .
Il pourrait être plus sûr à utiliser.
Il sera également en C++ 0x.

2
the_drow

Vous pouvez également faire plus de comparaisons dans votre instruction for:

for( vector<int>::iterator it = vec.begin(); it != vec.end() && it+1 != vec.end(); it+=2 ) {
    //manipulate the element through the iterator here
}

Je ne sais pas comment cela fonctionnerait vs Kostas's suggestion, mais il se sent comme il serait préférable pour un petit incrément . Bien sûr, il serait assez difficile à maintenir pour un incrément important, car vous avez besoin d'une vérification pour chacun, mais c'est une autre option.

Je l'éviterais certainement si possible. Si vous avez vraiment besoin d'incrémenter de 2 valeurs à la fois, alors envisagez d'avoir un vecteur de std :: pair ou un vecteur d'une structure à 2 éléments.

1
Dolphin

container.end() - l'élément juste après la fin - est la seule valeur extérieure définie.

Un itérateur vérifié sera en défaut sur ce qui est essentiellement un accès hors de portée, mais ce n'est pas très utile (d'autant que le comportement par défaut est de mettre fin au programme).

Je pense que la meilleure pratique est de "ne pas faire ça" - soit vérifier chaque valeur de l'itérateur (de préférence dans quelque chose enveloppé comme un filtre), et n'opérer que sur des entrées intéressantes, soit utiliser explicitement l'index avec

for(int i = 0; i < vec.size(); i+=2) {...}
1
Steve Gilham