본문으로 건너뛰기
Previous
Next
C++ Iterator Invalidation: “vector iterators incompatible”,

C++ Iterator Invalidation: “vector iterators incompatible”,

C++ Iterator Invalidation: “vector iterators incompatible”,

이 글의 핵심

STL iterator invalidation rules for vector, list, map, unordered_* and deque. Fix range-for + mutate bugs, use erase return values, erase–remove idiom, ASan, and MSVC iterator debugging.

Containers: [std::vector](/en/blog/cpp-stl-vector-complete/ · sequence basics: [arrays and lists](/en/blog/algorithm-series-01-array-list/.

Introduction: “Debug Assertion Failed: vector iterators incompatible”

“I erased in a loop and crashed”

Messages like vector iterators incompatible, list iterator not dereferencable, or map/set iterator not incrementable usually mean iterator invalidation: you kept using an iterator after an operation that ended its validity. That is undefined behavior—often a crash. Iterator invalidation happens when a container reallocates, inserts, erases, clears, or rehashes such that old iterators no longer refer to valid elements. This article covers:

  • Ten common invalidation patterns
  • Per-container rules (vector, list, map, unordered_*, deque)
  • Safe idioms: erase–remove, deferred deletion, index walks
  • Debugging: MSVC iterator checks, ASan, clang-tidy

Table of contents

  1. What is iterator invalidation?
  2. Per-container rules
  3. Ten bad patterns
  4. Safe coding patterns
  5. Debugging
  6. Production patterns
  7. Summary

1. What is iterator invalidation?

An iterator points at a container element. Operations that move or destroy storage can make old iterators stale—like a dangling pointer.

// 실행 예제
std::vector<int> vec = {1, 2, 3, 4, 5};
auto it = vec.begin();
vec.push_back(6);  // may reallocate → it may be invalid
std::cout << *it << '\n';  // undefined behavior

Release builds may “work” until they do not—use Debug + ASan early.

flowchart TB
  subgraph Before[Before change]
    M1[Memory at 0x1000]
    I1[iterator → 0x1000]
  end
  subgraph After[After reallocation]
    M2[Memory at 0x2000]
    I2[iterator → 0x1000 ❌]
  end
  Before -->|push_back realloc| After
  I2 -.->|dangling| Crash[Crash / UB]

2. Per-container rules (high level)

vector

OperationInvalidates
push_back / emplace_backAll iterators if reallocation
insertiterators at/after insertion point; all if reallocation
eraseiterators at/after erase point; references/pointers similarly
cleareverything
reserve / resizeall if reallocation
Rule of thumb: treat vector iterators as fragile across any operation that can reallocate or shift elements.

list / forward_list

Inserts do not invalidate other iterators. erase invalidates only the erased element.

map / set

Insert/erase behavior similar to list-like stability for non-erased iterators (tree structure).

unordered_map / unordered_set

Rehash invalidates iterators (and can change bucket structure). erase invalidates erased elements.

deque

Many operations invalidate all iterators (implementation-dependent details—treat as unstable for long-lived iterators).

3. Ten common bad patterns

1. Range-for + erase/remove inside the loop

Do not mutate the same container in a range-for in ways that invalidate its hidden iterators. Safe: classic loop with it = vec.erase(it), or erase–remove.

for (auto it = vec.begin(); it != vec.end(); ) {
    if (*it % 2 == 0) {
        it = vec.erase(it);
    } else {
        ++it;
    }
}

2. Range-for + push_back extending past original end

Save original_size and index [0, original_size), or build a separate container.

3. Iterator across insert without refresh

After insert, recompute iterators from fresh begin() + index or store indices instead of iterators.

4. reserve after saving iterators

Call reserve before taking iterators you intend to keep valid (or avoid storing iterators across reserve).

5. map erase + ++it blindly

Use it = m.erase(it) (C++11+) or erase post-increment idiom in older code.

6. Nested loops erase inner vector wrong

Inner loop must also use erase return pattern.

7. Saved iterator + later push_back

Long-lived iterators into vector are unsafe across growth—store index.

8. Concurrent iteration + mutation without sync

One thread push_back while another iterates → invalidation + data races—use mutex or separate snapshots.

9. Passing iterators into functions that grow the vector

Pass index or ensure no reallocation (e.g. reserved capacity contract).

10. Cached end() iterator across push_back

Call end() each iteration or compare against saved size for controlled algorithms.

4. Safe patterns

erase–remove (bulk predicate erase)

#include <algorithm>
#include <vector>
std::vector<int> vec = {1, 2, 3, 4, 5, 6};
vec.erase(
    std::remove_if(vec.begin(), vec.end(),
        [](int x) { return x % 2 == 0; }),
    vec.end()
);

Deferred deletion (mark + sweep)

Mark objects during update; erase(remove_if, end) after the traversal completes—common in games and event systems.

Index-based erase (note complexity)

Erasing by index can be O(n²) for many erases; prefer erase–remove for large removals.

unordered_map + reserve

Pre-size to reduce rehash iterator invalidation during growth.

References into vector

A reference to vec[i] is invalidated by reallocation—similar rules as iterators; reserve if you can bound growth.

5. Debugging

  • MSVC Debug STL: iterator checks (_ITERATOR_DEBUG_LEVEL).
  • ASan: often reports heap-use-after-free when iterators imply freed storage.
  • clang-tidy: rules like bugprone-inaccurate-erase help certain mistakes.

6. Production patterns

  • Entity managers: mark pending_destroy, sweep after tick.
  • Event buses: snapshot listeners (weak_ptr) or copy vector before notifying.
  • Thread-safe wrappers: single mutex for all container mutations + separate snapshot reads if needed.

7. Summary tables

Container quick reference

Containerinserteraserehash / realloc
vectorafter pos + maybe allafter pos + maybe alloften all iterators
liststableerased onlyN/A
map/setstableerased onlyN/A
unordered_*may rehasherased onlyrehash → invalidate iterators
dequeoften alloften all

Rules of thumb

  1. Do not modify a container in a range-for over that container unless the algorithm is proven safe.
  2. erase: it = c.erase(it).
  3. push_back: mind reallocation; reserve when you can bound size.
  4. Avoid long-lived iterators into vector/deque across mutations—prefer indices.
  5. If you must mutate during traversal, defer or work on a copy.

Implementation checklist

  • No range-for self-mutation without proof
  • Erase loops use returned iterators
  • reserve / size snapshots before growth loops
  • No stored iterators across push_back without reserve contract
  • Thread safety around shared containers
  • ASan tests for iterator-heavy code


Keywords

iterator invalidation, vector iterators incompatible, erase remove, push_back during iteration, range-based for

Closing: iterator invalidation is a top source of subtle crashes. Learn container rules, prefer erase–remove and deferred deletion, and validate with Debug STL + ASan.

Practical tips

  • Enable ASan on CI for iterator-heavy modules.
  • Code review checklist: range-for + mutation is a red flag.
  • Prefer algorithms that operate in one pass over ad-hoc erase loops when possible.

자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. STL iterator invalidation rules for vector, list, map, unordered_* and deque. Fix range-for + mutate bugs, use erase ret… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • [C++ std::vector Complete Guide: Usage, Iterators, and](/en/blog/cpp-stl-vector-complete/
  • [Arrays and Lists](/en/blog/algorithm-series-01-array-list/
  • C++ vector 기초 완벽 가이드 | 초기화·연산·용량 관리와 실전 패턴
  • C++ 반복자 기초 완벽 가이드 | iterator 카테고리·begin/end·역방향 반복자·실전 패턴
  • C++ Use After Free | ‘해제 후 사용’ 가이드

이 글에서 다루는 키워드 (관련 검색어)

C++, Iterator invalidation, vector, STL, Debugging, Undefined behavior 등으로 검색하시면 이 글이 도움이 됩니다.