Iterators are objects that allow referencing elements of STL containers similarly to pointers. They provide a uniform way to access elements of any container (vector, list, map, etc.). There are several types of iterators:
Special attention should be given to the lifetime of iterators:
Example:
std::vector<int> v = {1,2,3,4,5}; for (auto it = v.begin(); it != v.end(); ++it) { if (*it == 3) { // Remove the element with the value 3 it = v.erase(it); // erase returns an iterator to the next element --it; // correct the iterator if necessary } }
Question: When calling std::vector::insert, do the iterators get invalidated?
Common Answer: No, only when adding outside the end range.
Correct Answer: All iterators and references equal to or following the insertion position become invalid if the container's capacity is increased. If the capacity is sufficient — only the iterators in the range after the insertion point become invalid.
Example:
std::vector<int> v = {1,2,3}; auto it = v.begin() + 1; v.insert(v.begin(), 0); // it may be invalidated here!
Story: In a project, pointers were used to iterate over std::vector, and after push_back, the iteration continued over invalidated pointers, leading to application crashes.
Story: A developer deleted elements from std::list through erase in a for(auto it : list) loop without using the returned erase iterator, causing the iteration to skip elements and not delete all intended ones.
Story: The code used std::map, and after erase by key, the iterator remained tied to the deleted element (undefined behavior) — this led to random crashes during further accesses.