Quelle est la différence entre const_iterator et non-const iterator dans le C ++ STL?

139

Quelle est la différence entre a const_iteratoret an iteratoret où utiliseriez-vous l'un sur l'autre?

Konrad
la source
1
Eh bien, le nom const_iterator sonne comme l'itérateur est const, tandis que la chose vers laquelle cet itérateur pointe est le const réel.
talekeDskobeDa

Réponses:

124

const_iteratorLes s ne vous permettent pas de modifier les valeurs vers lesquelles ils pointent, les iterators ordinaires le font.

Comme pour toutes les choses en C ++, préférez toujours const, sauf s'il y a une bonne raison d'utiliser des itérateurs réguliers (c'est-à-dire que vous voulez utiliser le fait qu'ils ne constchangent pas la valeur pointée).

Dominic Rodger
la source
8
Dans un monde parfait, ce serait le cas. Mais avec C ++, const n'est aussi bon que la personne qui a écrit le code :(
JaredPar
mutable existe pour une très bonne raison. Il est rarement utilisé, et en regardant Google Code Search, il semble y avoir un pourcentage raisonnable d'utilisations valides. Le mot-clé est un outil d'optimisation très puissant, et ce n'est pas comme si le supprimer améliorerait la correction de la constance ( coughpointerscoughandcoughreferencescough )
coppro
2
Plus comme un hack puissant. De tous les exemples que j'ai jamais vus de l'utilisation du mot-clé «mutable», tous sauf un étaient un indicateur précis que le code était mal écrit et qu'il était nécessaire de le modifier pour contourner les défauts.
John Dibling
7
Il a des utilisations légitimes, comme la mise en cache des résultats d'un long calcul dans une classe const. D'un autre côté, c'est à peu près la seule fois que j'utilise un mutable en près de vingt ans de développement C ++.
Head Geek
Un exemple générique d'utilisation de const_iterator serait lorsque l'itérateur est une rvalue
talekeDskobeDa
40

Ils devraient être assez explicites. Si l'itérateur pointe vers un élément de type T, alors const_iterator pointe vers un élément de type 'const T'.

C'est fondamentalement équivalent aux types de pointeurs:

T* // A non-const iterator to a non-const element. Corresponds to std::vector<T>::iterator
T* const // A const iterator to a non-const element. Corresponds to const std::vector<T>::iterator
const T* // A non-const iterator to a const element. Corresponds to std::vector<T>::const_iterator

Un itérateur const pointe toujours vers le même élément, donc l'itérateur lui - même est const. Mais l'élément sur lequel il pointe n'a pas besoin d'être const, donc l'élément vers lequel il pointe peut être modifié. Un const_iterator est un itérateur qui pointe vers un élément const, donc bien que l'itérateur lui-même puisse être mis à jour (incrémenté ou décrémenté, par exemple), l'élément sur lequel il pointe ne peut pas être modifié.

jalf
la source
1
"Un itérateur const pointe toujours vers le même élément," Ceci est incorrect.
John Dibling
2
Comment? Notez le trait de soulignement manquant. Je compare une variable de type const std :: vector <T> :: iterator avec std :: vector <T> :: const_iterator. Dans le premier cas, l'itérateur lui-même est const, il ne peut donc pas être modifié, mais l'élément auquel il fait référence peut être modifié librement.
jalf
4
Ah, je vois. Oui, j'ai raté le trait de soulignement manquant.
John Dibling
3
@JohnDibling Upvoté pour avoir expliqué la subtilité entre const iterateret const_iterator.
legends2k
8

Malheureusement, de nombreuses méthodes pour les conteneurs STL prennent des itérateurs au lieu de const_iterators comme paramètres. Donc, si vous avez un const_iterator , vous ne pouvez pas dire "insérer un élément avant l'élément vers lequel cet itérateur pointe" (dire une telle chose n'est pas conceptuellement une violation de const, à mon avis). Si vous voulez quand même le faire, vous devez le convertir en un itérateur non const en utilisant std :: advance () ou boost :: next () . Par exemple. boost :: next (container.begin (), std :: distance (container.begin (), the_const_iterator_we_want_to_unconst)) . Si le conteneur est une liste std :: , le temps d'exécution de cet appel sera O (n) .

Ainsi, la règle universelle pour ajouter const partout où il est "logique" de le faire, est moins universelle en ce qui concerne les conteneurs STL.

Cependant, les conteneurs boost prennent des const_iterators (par exemple, boost :: unordered_map :: erase ()). Ainsi, lorsque vous utilisez des conteneurs boost, vous pouvez être "const agressif". Au fait, est-ce que quelqu'un sait si ou quand les conteneurs STL seront réparés?

Magnus Andermo
la source
1
C'est peut-être une question d'opinion. Dans le cas de vectoret deque, l'insertion d'un élément invalide tous les itérateurs existants, ce qui n'est pas très const. Mais je comprends votre point de vue. Ces opérations sont protégées par le caractère du conteneur const, pas par les itérateurs. Et je me demande pourquoi il n'y a pas de fonction de conversion d'itérateur const-to-nonconst dans l'interface de conteneur standard.
Potatoswatter
Vous avez raison Potatoswatter, je suis catégorique, c'est une question d'opinion pour les conteneurs à accès aléatoire et container.begin () + (the_const_iterator_we_want_to_unconst - container.begin ()) est de toute façon O (1) . Je me demande également pourquoi il n'y a pas de fonction de conversion pour les conteneurs à accès non aléatoire, mais il y a peut-être une bonne raison? Savez-vous s'il y a une raison pour laquelle les fonctions des conteneurs à accès non aléatoire ne prennent pas const_iterators ?
Magnus Andermo
"dire une telle chose n'est pas conceptuellement une violation de const, à mon avis" - c'est un commentaire très intéressant, j'ai les réflexions suivantes sur le sujet. Avec des pointeurs simples, on peut dire int const * foo; int * const foo;et int const * const foo;tous les trois sont valables et utiles, chacun à sa manière. std::vector<int> const bardevrait être le même que le second, mais il est malheureusement souvent traité comme le troisième. La cause première du problème est que nous ne pouvons pas dire std::vector<int const> bar;quand cela signifie qu'il n'y a aucun moyen d'obtenir le même effet que int const *foo;dans un vecteur.
dgnuff
5

Utilisez const_iterator chaque fois que vous le pouvez, utilisez iterator lorsque vous n'avez pas d'autre choix.

Naveen
la source
4

Exemples exécutables minimaux

Les itérateurs non const vous permettent de modifier ce qu'ils pointent:

std::vector<int> v{0};
std::vector<int>::iterator it = v.begin();
*it = 1;
assert(v[0] == 1);

Les itérateurs const ne:

const std::vector<int> v{0};
std::vector<int>::const_iterator cit = v.begin();
// Compile time error: cannot modify container with const_iterator.
//*cit = 1;

Comme indiqué ci-dessus, v.begin()est constsurchargé et retourne soit iteratorou en const_iteratorfonction de la const-ness de la variable de conteneur:

Un cas courant où const_iteratorapparaît est quand thisest utilisé dans une constméthode:

class C {
    public:
        std::vector<int> v;
        void f() const {
            std::vector<int>::const_iterator it = this->v.begin();
        }
        void g(std::vector<int>::const_iterator& it) {}
};

constfait thisconst, ce qui rend this->vconst.

Vous pouvez généralement l'oublier avec auto, mais si vous commencez à passer ces itérateurs, vous devrez y penser pour les signatures de méthode.

Tout comme const et non-const, vous pouvez facilement convertir de non-const en const, mais pas l'inverse:

std::vector<int> v{0};
std::vector<int>::iterator it = v.begin();

// non-const to const.
std::vector<int>::const_iterator cit = it;

// Compile time error: cannot modify container with const_iterator.
//*cit = 1;

// Compile time error: no conversion from const to no-const.
//it = ci1;

Lequel utiliser: analogue à const intvs int: préférez les itérateurs const chaque fois que vous pouvez les utiliser (lorsque vous n'avez pas besoin de modifier le conteneur avec eux), pour mieux documenter votre intention de lire sans modifier.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
0

(comme d'autres l'ont dit) const_iterator ne vous permet pas de modifier les éléments sur lesquels il pointe, c'est utile dans les méthodes de classe const. Cela vous permet également d'exprimer votre intention.

Trey Jackson
la source
0

ok Permettez-moi de l'expliquer avec un exemple très simple d'abord sans utiliser un itérateur constant considérons que nous avons une collection d'entiers aléatoires collection "randomData"

    for(vector<int>::iterator i = randomData.begin() ; i != randomData.end() ; ++i)*i = 0;
for(vector<int>::const_iterator i = randomData.begin() ; i!= randomData.end() ; ++i)cout << *i;

Comme on peut le voir pour écrire / éditer des données à l'intérieur de la collection, l'itérateur normal est utilisé mais pour la lecture, un itérateur constant a été utilisé. Si vous essayez d'utiliser un itérateur constant dans la première boucle for, vous obtiendrez une erreur. En règle générale, utilisez un itérateur constant pour lire les données à l'intérieur de la collection.

Monsieur Codeur
la source