[2026] C++ 반복자 기초 완벽 가이드 | iterator 카테고리·begin/end·역방향 반복자·실전 패턴

[2026] C++ 반복자 기초 완벽 가이드 | iterator 카테고리·begin/end·역방향 반복자·실전 패턴

이 글의 핵심

C++ 반복자 기초 : iterator 카테고리·begin/end·역방향 반복자·실전 패턴. 순회 중 erase했는데 프로그램이 죽어요부터 핵심 개념·패턴·실무 함정을 코드 예제로 풉니다.

들어가며: 순회 중 erase했는데 프로그램이 죽어요

”반복자로 순회하다가 erase 호출 후 크래시가 나요”

반복자(iterator)는 STL 컨테이너의 원소를 순회·접근하는 객체입니다. 포인터처럼 *it로 역참조, ++it로 다음 원소로 이동, it != end()로 비교할 수 있습니다. STL 알고리즘은 모두 [begin, end) 반복자 범위를 받아 동작하므로, 반복자를 이해해야 STL을 제대로 활용할 수 있습니다. 비유하면 반복자는 “책장의 책갈피”입니다. 책갈피를 한 칸씩 옮기면서 페이지를 읽듯, 반복자를 ++로 이동하면서 원소에 접근합니다. end()는 “마지막 페이지 다음”을 가리켜 미포함 범위 [begin, end)를 표현합니다. 문제의 코드: 아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ 나쁜 예: erase 후 반복자 무효화
std::vector<int> vec = {1, 2, 0, 3, 0, 4};
for (auto it = vec.begin(); it != vec.end(); ++it) {
    if (*it == 0) vec.erase(it);  // it 무효화 → 다음 ++it에서 미정의 동작!
}

위 코드 설명: vec.erase(it)it가 가리키던 원소를 제거하고, 그 이후의 모든 반복자를 무효화합니다. ++it를 하면 이미 무효화된 반복자를 사용해 미정의 동작이 됩니다. 해결법: 아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ 올바른 사용: erase가 반환하는 새 반복자 사용
for (auto it = vec.begin(); it != vec.end(); ) {
    if (*it == 0) it = vec.erase(it);
    else ++it;
}
// ✅ 또는 erase-remove idiom
vec.erase(std::remove(vec.begin(), vec.end(), 0), vec.end());

이 글을 읽으면:

  • 반복자 카테고리와 begin/end의 의미를 이해할 수 있습니다.
  • 역방향 반복자·const 반복자·범위 기반 for와의 관계를 알 수 있습니다.
  • 자주 겪는 에러와 해결법을 배울 수 있습니다.
  • 프로덕션에서 검증된 패턴을 활용할 수 있습니다. 다음은 mermaid를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 실행 예제
flowchart TB
  subgraph problem[자주 겪는 실패 시나리오]
    P1[erase 후 반복자 무효화]
    P2[end 역참조 → UB]
    P3[순회 중 push_back]
    P4[역방향 반복자 base 잘못 사용]
  end
  subgraph solution[올바른 선택]
    S1[erase 반환값 사용]
    S2[it != end 검사 후 역참조]
    S3[순회 중 수정 금지]
    S4[reverse_iterator.base 사용법]
  end
  P1 --> S1
  P2 --> S2
  P3 --> S3
  P4 --> S4

실무 적용 경험: 이 글은 대규모 C++ 프로젝트에서 실제로 겪은 문제와 해결 과정을 바탕으로 작성되었습니다. 책이나 문서에서 다루지 않는 실전 함정과 디버깅 팁을 포함합니다.

목차

  1. 문제 시나리오와 원인 분석
  2. 반복자 카테고리
  3. begin과 end 완전 가이드
  4. 역방향 반복자 (reverse_iterator)
  5. 반복자 어댑터
  6. std::distance와 std::advance
  7. 완전한 반복자 예제 모음
  8. 커스텀 반복자 구현
  9. 자주 발생하는 에러와 해결법
  10. 베스트 프랙티스
  11. 프로덕션 패턴
  12. 구현 체크리스트

1. 문제 시나리오와 원인 분석

시나리오 1: erase 루프에서 반복자 무효화

증상: 0을 제거하는 루프에서 vec.erase(it)++it 시 크래시. 원인: erase는 해당 원소를 제거하고, 그 위치 이후의 반복자들이 무효화됩니다. erase삭제된 원소의 다음을 가리키는 반복자를 반환하므로, it = vec.erase(it)로 받아야 합니다. 해결: it = vec.erase(it) 또는 erase-remove idiom 사용.

시나리오 2: find 반환값을 end()와 비교하지 않음

증상: *std::find(...)로 역참조 시 크래시. 원인: find는 값을 찾지 못하면 end()를 반환합니다. end()는 “마지막 원소 다음”을 가리키므로 역참조하면 미정의 동작입니다. 해결: auto it = std::find(...); if (it != vec.end()) { /* *it 사용 */ }

시나리오 3: 범위 기반 for 안에서 컨테이너 수정

증상: for (auto& x : vec) 루프 안에서 vec.push_back() 호출 시 크래시. 원인: 범위 기반 for는 내부적으로 begin/end를 사용합니다. 순회 중 push_back/insert/erase는 반복자를 무효화합니다. 해결: 순회 중에는 컨테이너를 수정하지 않거나, 인덱스 기반 루프 또는 erase-remove idiom 사용.

시나리오 4: 역방향 반복자에서 base() 잘못 사용

증상: reverse_iteratorerase에 넘기려 할 때 잘못된 원소가 삭제됨. 원인: reverse_iterator::base()는 “역방향으로 보던 마지막 원소의 다음”을 가리킵니다. rit.base()rit가 가리키는 논리적 위치가 다릅니다. 해결: vec.erase(std::next(rit).base()) 또는 vec.erase((++rit).base())로 삭제할 원소에 맞는 반복자 사용.

시나리오 5: const 반복자 vs 비const 반복자 혼용

증상: cbegin()/cend()로 얻은 반복자로 *it = 42 시 컴파일 에러. 원인: cbegin()/cend()는 const 반복자를 반환합니다. const 반복자로는 원소를 수정할 수 없습니다. 해결: 읽기 전용이면 cbegin/cend, 수정이 필요하면 begin/end 사용.

시나리오 6: 빈 컨테이너에서 begin == end

증상: 빈 vector에서 *vec.begin() 역참조 시 크래시. 원인: 빈 컨테이너에서는 begin() == end()입니다. begin()을 역참조하면 미정의 동작입니다. 해결: if (!vec.empty()) 또는 it != vec.end() 검사 후 역참조.

시나리오 7: std::distance에 역순 범위

증상: std::distance(last, first) 미정의 동작. 해결: std::distance(vec.begin(), it)로 항상 순방향 범위 전달.

시나리오 8: back_inserter 없이 빈 벡터에 copy

증상: std::copy(src.begin(), src.end(), dst.begin())에서 dst 비어 있으면 크래시. 해결: std::back_inserter(dst) 사용 또는 dst.resize(src.size()) 후 복사.

시나리오 9: map/set 순회 중 erase

증상: map/set 순회 중 erase(it)++it 시 크래시. 원인: erase(it)는 반복자를 무효화합니다. it = m.erase(it)로 반환값을 받아야 합니다. 해결: it = m.erase(it) 사용. C++20에서는 std::erase_if(m, pred) 활용.

2. 반복자 카테고리

C++ 표준은 반복자 카테고리를 계층적으로 정의합니다. 각 카테고리는 지원하는 연산이 다르며, 알고리즘은 “필요한 최소 카테고리”만 요구합니다.

카테고리 계층 구조

아래 코드는 mermaid를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

flowchart TD
    Input["Input Iteratorbr/읽기, 전진, 1회만"]
    Output["Output Iteratorbr/쓰기, 전진, 1회만"]
    Forward["Forward Iteratorbr/읽기/쓰기, 전..."]
    Bidirectional["Bidirectional Iteratorbr/전진..."]
    RandomAccess["Random Access Iteratorbr/임의..."]
    Input --> Forward
    Output --> Forward
    Forward --> Bidirectional
    Bidirectional --> RandomAccess

위 다이어그램 설명: Input/Output은 1회 스캔만 가능하고, Forward는 다회 순회, Bidirectional은 -- 지원, Random Access는 it + n, it[n] 지원합니다.

카테고리별 지원 연산

카테고리지원 연산예시 컨테이너
Input*it, ++it, it == it2istream_iterator
Output*it = x, ++itostream_iterator, back_inserter
ForwardInput + 다회 순회forward_list, unordered_*
BidirectionalForward + --itlist, map, set
Random AccessBidirectional + it + n, it[n], <vector, deque, array

카테고리 확인 예제

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iterator>
#include <vector>
#include <list>
#include <iostream>
int main() {
    std::vector<int> vec = {1, 2, 3};
    std::list<int> lst = {1, 2, 3};
    // vector: Random Access
    auto v_it = vec.begin();
    v_it += 2;           // OK
    int x = v_it[0];    // OK
    std::cout << "vector: " << x << "\n";
    // list: Bidirectional (Random Access 아님)
    auto l_it = lst.begin();
    ++l_it; ++l_it;     // OK
    // l_it += 2;       // 에러: list 반복자는 += 미지원
    std::cout << "list: " << *l_it << "\n";
}

위 코드 설명: vector는 연속 메모리이므로 it + n, it[n]이 O(1)입니다. list는 노드 기반이므로 +=가 없고, ++/--만 O(1)입니다. std::sort는 Random Access를 요구하므로 list에는 sort 멤버 함수를 사용해야 합니다.

iterator_traits로 카테고리 확인

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iterator>
#include <vector>
#include <list>
#include <type_traits>
int main() {
    using VIt = std::vector<int>::iterator;
    using LIt = std::list<int>::iterator;
    // iterator_category: Random Access vs Bidirectional
    static_assert(std::is_same_v<
        std::iterator_traits<VIt>::iterator_category,
        std::random_access_iterator_tag
    >);
    static_assert(std::is_same_v<
        std::iterator_traits<LIt>::iterator_category,
        std::bidirectional_iterator_tag
    >);
}

위 코드 설명: std::iterator_traits<It>::iterator_category로 해당 반복자의 카테고리를 컴파일 타임에 확인할 수 있습니다. 알고리즘 오버로딩이나 SFINAE에 활용됩니다.

3. begin과 end 완전 가이드

반개구간 [begin, end)

STL의 모든 범위는 반개구간(half-open range) [begin, end)를 사용합니다. begin은 첫 원소를, end마지막 원소의 다음을 가리킵니다. end는 역참조하면 안 됩니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <vector>
#include <iostream>
int main() {
    std::vector<int> vec = {10, 20, 30};
    // [begin, end) 시각화
    // vec:  [10] [20] [30]
    //        ^         ^
    //      begin     end (역참조 금지)
    auto it = vec.begin();
    std::cout << *it << "\n";  // 10
    ++it;
    std::cout << *it << "\n";  // 20
    ++it;
    std::cout << *it << "\n";  // 30
    ++it;
    // it == vec.end() → true
    // *it;  // ❌ 미정의 동작
}

위 코드 설명: [begin, end)는 “begin 포함, end 미포함”입니다. 원소 개수는 std::distance(begin, end) 또는 vec.size()와 같습니다. 빈 범위면 begin == end입니다.

begin() / end() vs std::begin() / std::end()

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <vector>
#include <array>
#include <iostream>
int main() {
    std::vector<int> vec = {1, 2, 3};
    std::array<int, 3> arr = {4, 5, 6};
    int c_arr[] = {7, 8, 9};
    // 멤버 함수 (컨테이너)
    auto v_b = vec.begin();
    auto v_e = vec.end();
    // std::begin/end (C 배열과 C++ 컨테이너 모두)
    auto a_b = std::begin(arr);
    auto a_e = std::end(arr);
    auto c_b = std::begin(c_arr);
    auto c_e = std::end(c_arr);
    std::cout << *v_b << " " << *a_b << " " << *c_b << "\n";  // 1 4 7
}

위 코드 설명: std::begin/std::end는 C 배열과 C++ 컨테이너 모두에 동작합니다. 제네릭 코드에서는 std::begin(r)/std::end(r)를 사용하면 배열·벡터·커스텀 타입을 모두 지원할 수 있습니다.

cbegin() / cend() — 읽기 전용

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <vector>
int main() {
    std::vector<int> vec = {1, 2, 3};
    auto it = vec.cbegin();
    int x = *it;   // OK: 읽기
    // *it = 42;   // 에러: const 반복자로 수정 불가
    auto it2 = vec.begin();
    *it2 = 42;     // OK: 비const 반복자
}

위 코드 설명: cbegin/cendconst_iterator를 반환합니다. 원소를 수정하지 않는 읽기 전용 순회에 사용하면, 실수로 수정하는 것을 방지할 수 있습니다.

빈 컨테이너 처리

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <vector>
#include <algorithm>
void process(const std::vector<int>& vec) {
    if (vec.empty()) return;  // 또는
    if (vec.begin() == vec.end()) return;
    auto it = std::find(vec.begin(), vec.end(), 42);
    if (it != vec.end()) {
        // *it 사용
    }
}

위 코드 설명: 빈 컨테이너에서는 begin() == end()이므로, find 등은 end()를 반환합니다. 역참조 전에 항상 it != end()를 확인하세요.

범위 기반 for와 begin/end

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <vector>
int main() {
    std::vector<int> vec = {1, 2, 3};
    // 범위 기반 for는 내부적으로 begin/end 사용
    for (auto& x : vec) {
        x *= 2;  // 수정 가능 (auto&)
    }
    // const 순회
    for (const auto& x : vec) {
        // x 수정 불가
    }
}

위 코드 설명: for (auto& x : range)begin(range)에서 end(range) 직전까지 순회합니다. rangestd::initializer_list이면 begin/end가 자동으로 호출됩니다.

4. 역방향 반복자 (reverse_iterator)

rbegin() / rend() 기본 사용

역방향 반복자는 끝에서 처음으로 순회합니다. rbegin()은 마지막 원소를, rend()는 첫 원소의 을 가리킵니다.

#include <vector>
#include <iostream>
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    // 역순 출력: 5 4 3 2 1
    for (auto rit = vec.rbegin(); rit != vec.rend(); ++rit) {
        std::cout << *rit << " ";
    }
    std::cout << "\n";
    // std::reverse_copy와 동일한 효과
    std::vector<int> reversed(vec.rbegin(), vec.rend());
    // reversed: {5, 4, 3, 2, 1}
}

위 코드 설명: ++rit는 역방향으로 한 칸 이동합니다(즉, 논리적으로 앞으로). rbegin()end()의 앞쪽, rend()begin()의 앞쪽에 대응됩니다.

reverse_iterator와 base()

reverse_iterator::base()는 “역방향으로 보던 마지막 원소의 다음”을 가리키는 일반 반복자를 반환합니다. 따라서 rit가 가리키는 원소를 지우려면 (std::next(rit)).base()를 사용해야 합니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <vector>
#include <iterator>
#include <iostream>
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    int target = 3;
    auto rit = std::find(vec.rbegin(), vec.rend(), target);
    if (rit != vec.rend()) {
        // rit가 가리키는 3을 지우려면: base()의 "앞" 위치
        // rit.base() = 3 다음 (4를 가리킴)
        // (++rit).base() = 3을 가리킴
        vec.erase((++rit).base());
        // 또는 vec.erase(std::next(rit).base());
    }
    // vec: {1, 2, 4, 5}
}

위 코드 설명: reverse_iterator가 가리키는 논리적 위치와 base()가 가리키는 위치는 한 칸 어긋나 있습니다. rit가 가리키는 원소를 지우려면 (++rit).base()erase에 넘깁니다.

역방향 순회 다이어그램

아래 코드는 mermaid를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

flowchart LR
    subgraph forward["정방향 (begin, end)"]
        B[begin] --> E[end]
    end
    subgraph reverse["역방향 (rbegin, rend)"]
        RB[rbegin] --> RE[rend]
    end
    B -.->|대응| RE
    E -.->|대응| RB

위 다이어그램 설명: rbegin()end()-1의 논리적 위치(마지막 원소)를, rend()begin()의 앞(첫 원소 앞)을 가리킵니다. ++rit는 정방향으로 보면 --에 해당합니다.

crbegin() / crend() — const 역방향

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

#include <vector>
void print(const std::vector<int>& vec) {
    for (auto rit = vec.crbegin(); rit != vec.crend(); ++rit) {
        std::cout << *rit << " ";  // 읽기만
        // *rit = 0;  // 에러
    }
}

위 코드 설명: crbegin/crend는 const 역방향 반복자를 반환합니다. 읽기 전용 역순 순회에 사용합니다.

5. 반복자 어댑터

반복자 어댑터는 다른 반복자나 컨테이너를 감싸서 새로운 동작을 제공합니다. STL 알고리즘에 “출력 대상”을 지정할 때 자주 사용합니다.

back_inserter — 벡터 끝에 추가

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <vector>
#include <algorithm>
#include <iterator>
#include <iostream>
int main() {
    std::vector<int> src = {1, 2, 3, 4, 5};
    std::vector<int> dst;
    // std::copy가 dst.end()에 쓰려 하면 UB
    // back_inserter는 push_back을 호출해 안전하게 추가
    std::copy(src.begin(), src.end(), std::back_inserter(dst));
    for (int x : dst) std::cout << x << " ";  // 1 2 3 4 5
}

위 코드 설명: std::back_inserter(dst)std::back_insert_iterator를 반환합니다. *it = valuedst.push_back(value)를 호출합니다. dst 크기를 미리 할당할 필요가 없습니다.

front_inserter — 리스트 앞에 삽입

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.

#include <list>
#include <algorithm>
#include <iterator>
std::vector<int> src = {1, 2, 3};
std::list<int> lst;
std::copy(src.begin(), src.end(), std::front_inserter(lst));
// lst: {3, 2, 1} — 역순 삽입

위 코드 설명: front_inserterpush_front를 사용합니다. list, deque만 지원. vector는 사용 불가.

insert_iterator — 특정 위치에 삽입

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.

#include <vector>
#include <algorithm>
#include <iterator>
std::vector<int> src = {10, 20, 30};
std::vector<int> dst = {1, 2, 3};
auto it = std::find(dst.begin(), dst.end(), 2);
std::copy(src.begin(), src.end(), std::inserter(dst, it));
// dst: {1, 10, 20, 30, 2, 3}

위 코드 설명: std::inserter(container, pos)pos 위치에 insert를 호출합니다.

ostream_iterator — 스트림 출력

#include <vector>
#include <algorithm>
#include <iterator>
#include <iostream>
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::copy(vec.begin(), vec.end(),
              std::ostream_iterator<int>(std::cout, "\n"));
    // 출력: 1\n2\n3\n4\n5
}

위 코드 설명: ostream_iterator<T>(stream, delim)*it = valuestream << value << delim를 호출합니다. 알고리즘 결과를 바로 출력할 때 유용합니다.

istream_iterator — 스트림 입력

다음은 간단한 cpp 코드 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.

#include <vector>
#include <iterator>
std::vector<int> vec(std::istream_iterator<int>(std::cin),
                     std::istream_iterator<int>());  // EOF까지

위 코드 설명: istream_iterator<T>()는 EOF를 나타냅니다. cin에서 EOF까지 읽어 vector를 채웁니다.

어댑터용도지원 컨테이너
back_inserter끝에 추가vector, deque, list, string
front_inserter앞에 삽입list, deque
inserter특정 위치 삽입대부분 STL 컨테이너
ostream_iterator스트림 출력cout, ofstream
istream_iterator스트림 입력cin, ifstream

6. std::distance와 std::advance

반복자 이동과 거리 계산에 std::advancestd::distance를 사용하면 컨테이너 종류에 관계없이 동작하는 제네릭 코드를 작성할 수 있습니다.

std::advance — 반복자 n칸 이동

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iterator>
#include <vector>
#include <list>
std::vector<int> vec = {1, 2, 3, 4, 5};
std::list<int> lst = {1, 2, 3, 4, 5};
auto v_it = vec.begin();
std::advance(v_it, 3);  // vector: O(1)
auto l_it = lst.begin();
std::advance(l_it, 3);  // list: O(n), ++ 루프
std::advance(l_it, -2);  // 음수: -- 로 역방향

위 코드 설명: std::advance(it, n)itn칸 이동합니다. Random Access면 O(1), Bidirectional면 O(n)입니다.

std::distance — 두 반복자 사이 거리

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iterator>
#include <vector>
#include <algorithm>
std::vector<int> vec = {10, 20, 30, 40, 50};
auto dist = std::distance(vec.begin(), vec.end());  // 5
auto it = std::find(vec.begin(), vec.end(), 40);
if (it != vec.end()) {
    size_t idx = std::distance(vec.begin(), it);  // 3
}

위 코드 설명: std::distance(first, last)[first, last) 원소 개수를 반환합니다. Random Access면 O(1), 그 외 O(n). firstlast보다 뒤면 미정의 동작입니다.

std::next와 std::prev

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iterator>
#include <vector>
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    // advance: it 자체 수정 (void)
    auto it1 = vec.begin();
    std::advance(it1, 2);
    // next: 원본 유지, 새 반복자 반환
    auto it2 = std::next(vec.begin(), 2);
    // prev: 역방향 next
    auto it3 = std::prev(vec.end(), 1);  // 마지막 원소
}

위 코드 설명: std::next(it, n)it를 수정하지 않고 n칸 이동한 복사본을 반환합니다. std::prev(it, n)은 역방향 이동입니다.

7. 완전한 반복자 예제 모음

예제 1: begin/end로 모든 컨테이너 순회

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <vector>
#include <list>
#include <array>
#include <iostream>
template<typename Container>
void print(const Container& c) {
    for (auto it = std::begin(c); it != std::end(c); ++it)
        std::cout << *it << " ";
    std::cout << "\n";
}
int main() {
    std::vector<int> vec = {1, 2, 3};
    std::list<int> lst = {4, 5, 6};
    int c_arr[] = {10, 11, 12};
    print(vec);
    print(lst);
    print(c_arr);
}

위 코드 설명: std::begin/std::end로 템플릿 하나로 모든 컨테이너와 C 배열을 처리할 수 있습니다.

예제 2: erase 루프 (조건부 삭제)

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

#include <vector>
std::vector<int> vec = {1, 0, 2, 0, 3, 0, 4};
for (auto it = vec.begin(); it != vec.end(); ) {
    if (*it == 0) it = vec.erase(it);
    else ++it;
}
// vec: {1, 2, 3, 4}

위 코드 설명: erase는 삭제된 원소의 다음 반복자를 반환합니다. it = vec.erase(it)로 받아야 합니다.

예제 3: 역방향으로 특정 값 찾아 삭제

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

#include <vector>
#include <algorithm>
#include <iterator>
std::vector<int> vec = {1, 2, 3, 2, 4, 2, 5};
auto rit = std::find(vec.rbegin(), vec.rend(), 2);
if (rit != vec.rend())
    vec.erase((++rit).base());  // 마지막 2 제거

위 코드 설명: findrbegin/rend를 넘기면 역방향으로 첫 번째(마지막에 있는) target을 반환합니다. (++rit).base()로 해당 원소를 가리키는 반복자를 얻습니다.

예제 4: iterator_traits 활용

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iterator>
#include <vector>
#include <list>
template<typename Iterator>
void advance_if_random_access(Iterator& it, int n) {
    if constexpr (std::is_same_v<
            typename std::iterator_traits<Iterator>::iterator_category,
            std::random_access_iterator_tag>)
        it += n;
    else {
        if (n >= 0) while (n--) ++it;
        else while (n++) --it;
    }
}
// vector: O(1), list: O(n)

위 코드 설명: iterator_traits로 카테고리를 확인해 Random Access면 +=, 아니면 ++/-- 루프를 사용합니다.

예제 5: 부분 범위 처리

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

#include <vector>
#include <algorithm>
std::vector<int> vec = {5, 2, 8, 1, 9, 3, 7};
std::sort(vec.begin(), vec.begin() + 3);  // 앞 3개만
auto it = std::find(vec.begin() + 2, vec.begin() + 5, 9);
if (it != vec.begin() + 5) {
    auto idx = std::distance(vec.begin(), it);  // 인덱스
}

위 코드 설명: vec.begin() + n으로 부분 범위 지정. Random Access에서만 + 지원. std::distance로 인덱스 계산.

예제 6: const 반복자로 읽기 전용 API

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <vector>
#include <algorithm>
bool contains(const std::vector<int>& vec, int value) {
    return std::find(vec.cbegin(), vec.cend(), value) != vec.cend();
}
int sum(const std::vector<int>& vec) {
    int s = 0;
    for (auto it = vec.cbegin(); it != vec.cend(); ++it) s += *it;
    return s;
}

위 코드 설명: const 참조 함수에서는 cbegin/cend로 수정 불가를 명시합니다.

8. 커스텀 반복자 구현

자체 컨테이너나 특수한 순회 로직이 필요할 때 커스텀 반복자를 구현할 수 있습니다. iterator_traits를 특수화하거나 std::iterator_traits가 자동 추론할 수 있도록 필요한 타입을 정의합니다.

최소 요구사항 (Random Access Iterator)

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iterator>
#include <algorithm>
#include <iostream>
template<typename T, size_t N>
class FixedArray {
public:
    T data[N];
    class Iterator {
    public:
        using value_type = T;
        using difference_type = std::ptrdiff_t;
        using iterator_category = std::random_access_iterator_tag;
        using pointer = T*;
        using reference = T&;
        explicit Iterator(T* ptr) : ptr_(ptr) {}
        reference operator*() const { return *ptr_; }
        Iterator& operator++() { ++ptr_; return *this; }
        Iterator& operator--() { --ptr_; return *this; }
        Iterator& operator+=(difference_type n) { ptr_ += n; return *this; }
        Iterator operator+(difference_type n) const { return Iterator(ptr_ + n); }
        Iterator operator-(difference_type n) const { return Iterator(ptr_ - n); }
        difference_type operator-(const Iterator& other) const { return ptr_ - other.ptr_; }
        bool operator==(const Iterator& other) const { return ptr_ == other.ptr_; }
        bool operator!=(const Iterator& other) const { return ptr_ != other.ptr_; }
        bool operator<(const Iterator& other) const { return ptr_ < other.ptr_; }
    private:
        T* ptr_;
    };
    Iterator begin() { return Iterator(data); }
    Iterator end() { return Iterator(data + N); }
};
int main() {
    FixedArray<int, 5> arr = {{1, 2, 3, 4, 5}};
    std::sort(arr.begin(), arr.end());
    auto it = std::find(arr.begin(), arr.end(), 3);
    if (it != arr.end())
        std::cout << "Index: " << std::distance(arr.begin(), it) << "\n";
}

위 코드 설명: iterator_category, value_type, difference_type 등을 정의하면 std::iterator_traits가 자동으로 사용합니다. begin/end만 제공하면 범위 기반 for와 STL 알고리즘을 사용할 수 있습니다.

범위 기반 for 지원

다음은 간단한 cpp 코드 예제입니다. 반복문으로 데이터를 처리합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// begin/end만 정의하면 범위 기반 for 자동 지원
for (int x : arr) {
    std::cout << x << " ";
}

위 코드 설명: begin()end()를 제공하면 for (auto x : range)가 자동으로 동작합니다. std::begin/std::end도 이 멤버를 호출합니다.

9. 자주 발생하는 에러와 해결법

에러 1: erase 후 반복자 무효화

증상: vec.erase(it)++it 시 크래시. 해결법:

// ❌ for (auto it = ...; ++it) { if (*it==0) vec.erase(it); }
// ✅ for (auto it = ...; ) { if (*it==0) it=vec.erase(it); else ++it; }

에러 2: find 반환값을 end()와 비교하지 않음

증상: *std::find(...) 역참조 시 크래시 (없으면 end() 반환). 해결법:

// ❌ int value = *std::find(...);
// ✅ auto it = std::find(...); if (it != vec.end()) use(*it);

에러 3: 범위 기반 for 안에서 컨테이너 수정

증상: for (auto& x : vec) 안에서 vec.push_back() 시 크래시. 해결법: 순회 중 수정 금지. 별도 컨테이너에 모은 뒤 insert로 일괄 추가.

에러 4: reverse_iterator의 base() 잘못 사용

증상: rit가 가리키는 원소를 지우려 했는데 다른 원소가 삭제됨. 해결법: rit.base()는 rit 다음을 가리킴 → vec.erase((++rit).base()) 사용.

에러 5: 빈 컨테이너에서 begin() 역참조

증상: 빈 vector에서 *vec.begin() 시 크래시. 원인: 빈 컨테이너에서는 begin() == end()입니다. 해결법: 아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ 잘못된 사용
std::vector<int> vec;
int x = *vec.begin();  // UB
// ✅ 올바른 사용
if (!vec.empty()) {
    int x = *vec.begin();
}

에러 6: list에 std::sort 사용

증상: std::sort(lst.begin(), lst.end()) 컴파일 에러. 해결법: list는 Bidirectional → lst.sort() 멤버 함수 사용.

에러 7: 반복자 범위 [begin, end) 혼동

증상: end를 포함해 처리하려다 범위 초과. 해결법: [begin, end)는 end 미포함. end 역참조 금지.

에러 8: 반복자 복사 후 원본 컨테이너 수정

증상: 반복자 저장 후 push_back 호출, 이후 사용 시 크래시. 해결법: push_back/insert 후 반복자 재획득.

에러 9~11: 어댑터·distance·연산자

  • 9. vector에 front_inserter: vectorpush_front 없음 → list/deque 사용.
  • 10. distance 역순: distance(it, begin) 미정의 → distance(begin, it) 사용.
  • 11. list에 it + n: list는 Bidirectional → std::next(it, n) 사용.

10. 베스트 프랙티스

1. 반복자 범위 [begin, end) 일관 사용

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ begin, end 쌍으로 범위 전달
std::sort(vec.begin(), vec.end());
// ✅ 부분 범위
std::sort(vec.begin() + 2, vec.end() - 1);

2. 읽기 전용이면 cbegin/cend 사용

다음은 간단한 cpp 코드 예제입니다. 반복문으로 데이터를 처리합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ const 순회
for (auto it = vec.cbegin(); it != vec.cend(); ++it) {
    process(*it);
}

3. 역참조 전 end() 검사

아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ find 반환값 검사
auto it = std::find(vec.begin(), vec.end(), value);
if (it != vec.end()) {
    use(*it);
}

4. 제네릭 코드에서는 std::begin/end

아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

template<typename Range>
void process(Range& r) {
    for (auto it = std::begin(r); it != std::end(r); ++it) {
        // C 배열, vector, array 등 모두 지원
    }
}

5. erase-remove idiom 선호

// ✅ 조건 삭제 시 erase-remove
vec.erase(std::remove_if(vec.begin(), vec.end(),
     { return x == 0; }), vec.end());

6. iterator_traits로 카테고리 확인

아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

template<typename It>
void advance_impl(It& it, int n, std::random_access_iterator_tag) {
    it += n;
}
template<typename It>
void advance_impl(It& it, int n, std::bidirectional_iterator_tag) {
    while (n > 0) { ++it; --n; }
    while (n < 0) { --it; ++n; }
}

7. 제네릭 이동·출력

std::advance(it, n);  // 컨테이너 무관
auto mid = std::next(vec.begin(), vec.size() / 2);
std::transform(src.begin(), src.end(), std::back_inserter(result), f);

11. 프로덕션 패턴

패턴 1: 안전한 erase 루프

아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

template<typename Container, typename Predicate>
void erase_if(Container& c, Predicate pred) {
    for (auto it = c.begin(); it != c.end(); ) {
        if (pred(*it)) it = c.erase(it);
        else ++it;
    }
}

패턴 2: 역방향 검색 후 삭제

아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 마지막으로 나오는 target 제거
auto rit = std::find(vec.rbegin(), vec.rend(), target);
if (rit != vec.rend()) {
    vec.erase((++rit).base());
}

패턴 3: 반복자 유효성 검사

다음은 간단한 cpp 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

bool is_valid(const std::vector<int>& vec,
              std::vector<int>::const_iterator it) {
    return it >= vec.begin() && it < vec.end();
}

패턴 4: 부분 범위 알고리즘

std::partial_sort(vec.begin(), vec.begin() + 100, vec.end());
auto mid = std::next(vec.begin(), vec.size() / 2);
std::transform(vec.begin(), mid, result.begin(), f);

패턴 5: const 반복자로 API 설계

아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

class DataStore {
public:
    auto begin() const { return data_.cbegin(); }
    auto end() const { return data_.cend(); }
private:
    std::vector<int> data_;
};

패턴 6: 빈 범위 처리

아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

template<typename Range>
void safe_process(Range& r) {
    if (std::begin(r) == std::end(r)) return;
    // ...
}

12. 구현 체크리스트

반복자 사용 체크리스트

  • 역참조 전 it != end() 검사하는가?
  • erase 후 반환값을 받아 it = vec.erase(it) 하는가?
  • 순회 중에는 컨테이너를 수정하지 않는가?
  • reverse_iterator 삭제 시 (++rit).base() 사용하는가?
  • 읽기 전용이면 cbegin/cend 사용하는가?

에러 방지 체크리스트

  • 빈 컨테이너에서 begin() 역참조하지 않는가?
  • reverse_iterator::base() 직접 erase에 넘기지 않는가?
  • liststd::sort 대신 std::list::sort 사용하는가?
  • 반복자 저장 후 push_back/insert 시 재획득하는가?

실무 팁

개발 시 주의사항

  1. [팁 1]: [설명]
    // 예시 코드
  2. [팁 2]: [설명]
    // 예시 코드
  3. [팁 3]: [설명]

디버깅 방법

  • [방법 1]: [설명]
  • [방법 2]: [설명]
  • [방법 3]: [설명]

FAQ

Q: “begin과 end 중 end는 왜 역참조하면 안 되나요?”

A: end()는 “마지막 원소의 다음”을 가리킵니다. 유효한 원소가 없으므로 역참조하면 미정의 동작입니다. [begin, end) 반개구간으로 “첫 원소부터 마지막 원소까지”를 표현합니다.

Q: “vector와 list의 반복자 차이는?”

A: vector는 Random Access(it + n, it[n] 지원), list는 Bidirectional(++, --만). std::sort는 Random Access를 요구하므로 list에는 lst.sort() 멤버 함수를 사용합니다.

Q: “범위 기반 for와 반복자, 뭘 쓰나요?”

A: 단순 순회면 for (auto& x : vec)가 간결합니다. erase·인덱스·조건부 삭제가 필요하면 반복자 루프를 사용하세요.

Q: “reverse_iterator.base()가 헷갈려요.”

A: rit가 가리키는 원소를 지우려면 (++rit).base()를 사용합니다. rit.base()rit가 가리키는 원소의 다음을 가리킵니다.

Q: “프로덕션에서 주의할 점은?”

A: erase 반환값 사용, find 반환값 end() 비교, 순회 중 수정 금지, reverse_iterator base 사용법, 빈 컨테이너 처리.

참고 자료


한 줄 요약: 반복자는 STL의 핵심. begin/end는 [begin, end) 반개구간이고, erase 후 반환값 사용, find 반환값 end() 비교, 순회 중 수정 금지, reverse_iterator base 사용법을 지키면 안전합니다. 다음으로 STL 알고리즘 기초를 읽어보면 좋습니다. 이전 글: C++ vector 기초 | 초기화·연산·용량 관리와 실전 패턴
다음 글: C++ STL 알고리즘 | sort·find·transform 람다와 함께 쓰기

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

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

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

C++, 반복자, iterator, std::begin, std::end, reverse_iterator, iterator_traits, iterator_adapters, std::distance, std::advance 등으로 검색하시면 이 글이 도움이 됩니다.

관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3