Iterators
Every STL container — vector, list, map, set, and the rest — stores its elements differently in memory. A vector is contiguous, a list is a chain of linked nodes, a map is a tree. If every algorithm had to know the internal layout of every container, the STL would fall apart. Iterators solve this: an iterator is a generalized, pointer-like object that knows how to move to the "next" element of whatever container it belongs to, without the calling code needing to know how that traversal actually works under the hood.
begin() and end()
Manual iterator loop vs range-based for
#include <iostream>
#include <vector>
int main() {
std::vector<int> nums = {10, 20, 30, 40};
// Manual iterator loop
for (std::vector<int>::iterator it = nums.begin(); it != nums.end(); ++it) {
std::cout << *it << " ";
}
std::cout << "\n";
// Read-only traversal with a const_iterator
for (auto it = nums.cbegin(); it != nums.cend(); ++it) {
std::cout << *it << " ";
}
std::cout << "\n";
// Range-based for loop: the compiler expands this into
// the exact same begin()/end()/operator++ pattern above.
for (int n : nums) {
std::cout << n << " ";
}
std::cout << "\n";
return 0;
}Iterator categories
Category | Supports | Example containers |
|---|---|---|
Input | Single-pass read, ++ forward | istream_iterator, input streams |
Output | Single-pass write, ++ forward | ostream_iterator, back_inserter |
Forward | Multi-pass read/write, ++ forward | forward_list, unordered_map/set |
Bidirectional | Forward capabilities + -- backward | list, set, map, multiset, multimap |
Random Access | Bidirectional + jump by n in O(1) (it + n, it[n]) | vector, deque, array, C-style arrays |
Modifying a container while iterating
Iterators are only valid as long as the container's internal state matches what they were obtained from. Certain operations — inserting into a full vector (triggering reallocation), or erasing an element — invalidate some or all outstanding iterators into that container.
The wrong way to erase while iterating
#include <vector>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
// BUG: erase() invalidates 'it', so ++it on the next
// iteration is undefined behavior.
for (auto it = nums.begin(); it != nums.end(); ++it) {
if (*it % 2 == 0) {
nums.erase(it); // invalidates it and everything after it
}
}
// CORRECT: erase() returns a valid iterator to the next
// element, so reassign it instead of blindly incrementing.
std::vector<int> nums2 = {1, 2, 3, 4, 5};
for (auto it = nums2.begin(); it != nums2.end(); ) {
if (*it % 2 == 0) {
it = nums2.erase(it);
} else {
++it;
}
}
return 0;
}Iterators are the uniform interface that lets one algorithm work across many container types.
.begin()/.end() define a half-open range; .cbegin()/.cend() give read-only access.
The range-based for loop is compiler sugar over the manual iterator pattern.
Five categories — input, output, forward, bidirectional, random access — form a capability hierarchy.
Never use an iterator after an operation that may have invalidated it.