Was ist der Unterschied zwischen const_iterator und non-const iterator in der C ++ STL?


139

Was ist der Unterschied zwischen a const_iteratorund an iteratorund wo würden Sie einen über den anderen verwenden?


1
Nun, der Name const_iterator klingt so, als wäre der Iterator const, während das, worauf dieser Iterator zeigt, die tatsächliche const ist.
TalekeDskobeDa

Antworten:


124

const_iteratorMit s können Sie die Werte, auf die sie zeigen, nicht ändern iterator.

Wie bei allen Dingen in C ++ immer bevorzugen const, es sei denn, es gibt einen guten Grund, reguläre Iteratoren zu verwenden (dh Sie möchten die Tatsache nutzen, dass sie constden angegebenen Wert nicht ändern).


8
In einer perfekten Welt wäre das der Fall. Aber mit C ++ ist const nur so gut wie die Person, die den Code geschrieben hat :(
JaredPar

veränderlich existiert aus einem sehr guten Grund. Es wird selten verwendet, und bei Betrachtung der Google-Codesuche scheint es einen angemessenen Prozentsatz gültiger Verwendungen zu geben. Das Schlüsselwort ist ein sehr leistungsfähiges Optimierungswerkzeug, und es ist nicht so, als würde das Entfernen die Korrektheit verbessern ( Hustenzeigerhusten und Hustenanfälle )
coppro

2
Eher wie ein mächtiger Hack. Von allen Fällen, die ich jemals von der Verwendung des Schlüsselworts "veränderlich" gesehen habe, waren alle bis auf einen ein genauer Indikator dafür, dass der Code schlecht geschrieben und veränderbar war, um die Fehler zu umgehen.
John Dibling

7
Es hat legitime Verwendungszwecke, wie das Zwischenspeichern der Ergebnisse einer langen Berechnung innerhalb einer const-Klasse. Andererseits ist dies so ziemlich das einzige Mal, dass ich in fast zwanzig Jahren C ++ - Entwicklung eine veränderbare Version verwendet habe.
Head Geek

Ein generisches Beispiel für die Verwendung von const_iterator wäre, wenn der Iterator ein rvalue ist
talkeDskobeDa

40

Sie sollten so ziemlich selbsterklärend sein. Wenn der Iterator auf ein Element vom Typ T zeigt, zeigt const_iterator auf ein Element vom Typ 'const T'.

Es entspricht im Grunde den Zeigertypen:

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

Ein const-Iterator zeigt immer auf dasselbe Element, daher ist der Iterator selbst const. Das Element, auf das es zeigt, muss jedoch nicht const sein, sodass das Element, auf das es zeigt, geändert werden kann. Ein const_iterator ist ein Iterator, der auf ein const-Element verweist. Während der Iterator selbst aktualisiert werden kann (z. B. inkrementiert oder dekrementiert), kann das Element, auf das er zeigt, nicht geändert werden.


1
"Ein Konstantenator zeigt immer auf dasselbe Element." Dies ist falsch.
John Dibling

2
Wie? Beachten Sie den fehlenden Unterstrich. Ich kontrastiere eine Variable vom Typ const std :: vector <T> :: iterator mit std :: vector <T> :: const_iterator. Im ersteren Fall ist der Iterator selbst const, kann also nicht geändert werden, aber das Element, auf das er verweist, kann frei geändert werden.
Jalf

4
Ah ich sehe. Ja, ich habe den fehlenden Unterstrich verpasst.
John Dibling

3
@ JohnDibling Upvoted für die Erklärung der Subtilität zwischen const iteraterund const_iterator.
Legends2k

8

Leider verwenden viele Methoden für die STL-Container Iteratoren anstelle von const_iterators als Parameter. Wenn Sie also einen const_iterator haben , können Sie nicht sagen "Ein Element vor dem Element einfügen, auf das dieser Iterator zeigt" (so etwas zu sagen ist meiner Meinung nach konzeptionell keine Konstantenverletzung). Wenn Sie dies trotzdem tun möchten, müssen Sie es mit std :: advanced () oder boost :: next () in einen nicht konstanten Iterator konvertieren . Z.B. boost :: next (container.begin (), std :: distance (container.begin (), the_const_iterator_we_want_to_unconst)) . Wenn der Container eine std :: -Liste ist , beträgt die Laufzeit für diesen Aufruf O (n) .

Daher ist die universelle Regel, const hinzuzufügen, wo immer dies "logisch" ist, weniger universell, wenn es um STL-Container geht.

Boost-Container benötigen jedoch const_iterators (z. B. boost :: unordered_map :: erase ()). Wenn Sie also Boost-Container verwenden, können Sie "const agressive" sein. Weiß übrigens jemand, ob oder wann die STL-Container repariert werden?


1
Es kann Ansichtssache sein. Im Fall von vectorund macht das dequeEinfügen eines Elements alle vorhandenen Iteratoren ungültig, was nicht sehr ist const. Aber ich verstehe deinen Standpunkt. Solche Operationen sind durch Container const-ness geschützt , nicht durch die Iteratoren. Und ich frage mich, warum es in der Standard-Container-Schnittstelle keine Iterator-Konvertierungsfunktion von Konstante zu Nicht-Konstante gibt.
Potatoswatter

Sie sind richtig Potatoswatter, ich bin zu kategorisch, es ist Ansichtssache für die Direktzugriffscontainer und container.begin () + (the_const_iterator_we_want_to_unconst - container.begin ()) ist sowieso O (1) . Ich frage mich auch, warum es keine Konvertierungsfunktion für die Container mit nicht wahlfreiem Zugriff gibt, aber vielleicht gibt es einen guten Grund? Wissen Sie, ob es einen Grund gibt, warum die Funktionen für die Container mit nicht wahlfreiem Zugriff keine const_iterators annehmen ?
Magnus Andermo

"so etwas zu sagen ist meiner Meinung nach konzeptionell keine konstante Verletzung" - dies ist ein sehr interessanter Kommentar, ich habe die folgenden Gedanken zu diesem Thema. Mit einfachen Zeigern kann man sagen int const * foo; int * const foo;und int const * const foo;alle drei sind gültig und nützlich, jeder auf seine Weise. std::vector<int> const barsollte das gleiche sein wie das zweite, aber es wird leider oft wie das dritte behandelt. Die Hauptursache des Problems ist, dass wir nicht sagen können, std::vector<int const> bar;wann es keine Möglichkeit gibt, den gleichen Effekt wie int const *foo;in einem Vektor zu erzielen .
dgnuff

5

Verwenden const_iterator , wann immer Sie können, verwenden Iterator , wenn Sie keine andere Wahl haben.


4

Minimale ausführbare Beispiele

Mit nicht konstanten Iteratoren können Sie ändern, worauf sie verweisen:

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

Konst Iteratoren nicht:

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;

Wie oben gezeigt, v.begin()ist constüberladen und gibt entweder iteratoroder const_iteratorabhängig von der Konstanz der Containervariablen zurück:

Ein häufiger Fall, in dem const_iteratorPopups angezeigt werden, thisist die Verwendung innerhalb einer constMethode:

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) {}
};

constmacht thisconst, was this->vconst macht .

Normalerweise können Sie es mit vergessen auto, aber wenn Sie anfangen, diese Iteratoren weiterzugeben, müssen Sie für die Methodensignaturen darüber nachdenken.

Ähnlich wie bei const und non-const können Sie problemlos von non-const in const konvertieren, aber nicht umgekehrt:

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;

Welches zu verwenden ist: Analog zu const intvs int: Bevorzugen Sie konstante Iteratoren, wann immer Sie sie verwenden können (wenn Sie den Container nicht mit ihnen ändern müssen), um Ihre Absicht des Lesens ohne Änderung besser zu dokumentieren.


0

(wie andere gesagt haben) Mit const_iterator können Sie die Elemente, auf die es zeigt, nicht ändern. Dies ist innerhalb der const-Klassenmethoden nützlich. Sie können damit auch Ihre Absicht zum Ausdruck bringen.


0

ok Lassen Sie es mich zunächst anhand eines sehr einfachen Beispiels erklären, ohne einen konstanten Iterator zu verwenden. Denken Sie daran, dass wir eine Sammlung von zufälligen Ganzzahlen haben.

    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;

Wie zum Schreiben / Bearbeiten von Daten innerhalb der Sammlung zu sehen ist, wird ein normaler Iterator verwendet, aber zu Lesezwecken wurde ein konstanter Iterator verwendet. Wenn Sie versuchen, zuerst einen konstanten Iterator für die Schleife zu verwenden, wird eine Fehlermeldung angezeigt. Verwenden Sie als Faustregel einen konstanten Iterator, um Daten in der Sammlung zu lesen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.