Step 6 of 7
Iterator invalidation
Changing a container can invalidate iterators, pointers and references into it. Using one afterwards is undefined behavior, and one of the most common C++ bugs in real code. The program might work, crash, or quietly corrupt data.
| Container | What invalidates |
|---|---|
vector |
push_back that reallocates: everything. erase: the erased element and everything after it |
deque |
inserting at either end invalidates iterators (but not references) |
list, map, set |
only iterators to erased elements |
The safe erase-while-looping pattern uses the iterator erase returns, which is the element after the removed one:
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> stock = {{"apples", 0}, {"bread", 4}, {"eggs", 0}, {"milk", 2}};
int removed = 0;
for (auto it = stock.begin(); it != stock.end(); ) {
if (it->second == 0) {
std::cout << "out of " << it->first << "\n";
it = stock.erase(it); // erase returns the next position
removed++;
} else {
++it; // only advance when nothing was erased
}
}
std::cout << removed << " removed, left:";
for (const auto& [item, n] : stock) std::cout << " " << item << "=" << n;
std::cout << "\n";
}
out of apples
out of eggs
2 removed, left: bread=4 milk=2
Why the loop is shaped like this
- The
forhas no++itin its header. Advancing happens in exactly one of the two branches. - After
erase(it), the olditis dead. Writingstock.erase(it); ++it;increments a dead iterator: undefined behavior. it = stock.erase(it)already points at the next element, so incrementing again would skip one.
Other traps to know
- Holding a reference or pointer to
v[0]and then callingv.push_back(...). If the vector reallocated, the reference now points at freed memory. - A range-based
forover a container that the loop body modifies. It uses iterators behind the scenes, so the same rules apply.
For vectors, std::erase_if(v, pred) does removal in one efficient call, as you saw in the algorithms module. The loop pattern is what you need for maps and for loops that do more than erase.
Your turn: write int remove_expired(std::map<std::string, int>& sessions, int now) that erases every session whose expiry time is <= now, returning how many were removed. Use the loop pattern.
Previous: Iterators Next: Challenge: callbacks with std::function