C++ 범위 기반 for문과 구조화된 바인딩 | 모던 C++ 반복문

C++ 범위 기반 for문과 구조화된 바인딩 | 모던 C++ 반복문

이 글의 핵심

C++ 범위 기반 for문과 구조화된 바인딩에 대해 정리한 개발 블로그 글입니다. 컨테이너를 순회할 때마다 반복자 코드를 길게 작성해야 했습니다. 범위 기반 for(range-based for—for (auto& x : container) 형태로 컨테이너 전체를 순회하는 문법)는 "시작~끝"을 컴파일러가… 개념과 예제 코드를 단계적으로 다루며, 실무·학습에 참고할 수 있도록 구성했습니다. …

들어가며: 반복자 코드가 너무 길다

”for (auto it = vec.begin(); it != vec.end(); ++it)를 매번 쓰기 싫어요”

컨테이너를 순회할 때마다 반복자 코드를 길게 작성해야 했습니다. 범위 기반 for(range-based for—for (auto& x : container) 형태로 컨테이너 전체를 순회하는 문법)는 “시작~끝”을 컴파일러가 알아서 처리하므로 반복자 실수(끝 조건 잘못 쓰기, 타입 길게 쓰기)를 줄여 줍니다. 구조화된 바인딩(structured binding—auto [key, value]처럼 pair·tuple·구조체를 변수로 풀어 받는 C++17 문법)과 함께 쓰면 map·pair 순회가 한 줄로 읽기 쉬워져서, 실무에서 컨테이너를 다룰 때 기본으로 쓰는 문법이 됩니다.

문제의 코드:

std::vector<int> numbers = {1, 2, 3, 4, 5};

// ❌ 길고 복잡
for (std::vector<int>::iterator it = numbers.begin();
     it != numbers.end(); ++it) {
    std::cout << *it << "\n";
}

// map은 더 복잡
std::map<std::string, int> scores;
for (std::map<std::string, int>::iterator it = scores.begin();
     it != scores.end(); ++it) {
    std::cout << it->first << ": " << it->second << "\n";
}

범위 기반 for로 해결:

// 복사해 붙여넣은 뒤: g++ -std=c++17 -o range_for range_for.cpp && ./range_for
#include <iostream>
#include <vector>
#include <map>
#include <string>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    for (int num : numbers) {
        std::cout << num << "\n";
    }

    std::map<std::string, int> scores = {{"alice", 90}, {"bob", 80}};
    for (const auto& [name, score] : scores) {
        std::cout << name << ": " << score << "\n";
    }
    return 0;
}

주의사항: for (int num : numbers)int를 복사합니다. 큰 타입이면 const auto&를 검토하세요.

실행 결과: 1~5가 각각 한 줄씩 출력된 뒤, alice: 90, bob: 80 이 출력됩니다.

주의: 범위 기반 for는 “전체 구간을 순회”할 때만 사용할 수 있습니다. 반복 중에 요소를 삭제하거나, 반복자 위치를 건너뛰어야 하면 일반 for문과 반복자를 써야 하고, vector를 순회하면서 erase할 때는 erase-remove 관용구나 반복자 무효화를 조심해야 합니다.

이 글을 읽으면:

  • 범위 기반 for문을 올바르게 사용할 수 있습니다.
  • 구조화된 바인딩으로 코드를 간결하게 만들 수 있습니다.
  • 복사를 피하고 성능을 최적화할 수 있습니다.
  • 실전에서 자주 쓰는 반복 패턴을 익힐 수 있습니다.

실무에서 겪는 문제 시나리오

시나리오 1: 반복자 타입 오타로 컴파일 에러

// ❌ 문제: iterator vs const_iterator 혼동
std::vector<int> vec = {1, 2, 3};
for (std::vector<int>::const_iterator it = vec.begin();
     it != vec.end(); ++it) {
    *it = 10;  // 컴파일 에러: const_iterator는 수정 불가
}

// ✅ 해결: 범위 기반 for + auto
for (auto& num : vec) {
    num = 10;  // 의도가 명확
}

시나리오 2: map 순회 시 pair.first/second 반복 타이핑

// ❌ 문제: pair 접근이 장황함
std::map<std::string, int> config;
for (const auto& entry : config) {
    if (entry.first == "timeout") {
        int val = entry.second;
        // ...
    }
}

// ✅ 해결: 구조화된 바인딩
for (const auto& [key, value] : config) {
    if (key == "timeout") {
        int val = value;
        // ...
    }
}

시나리오 3: 대용량 컨테이너에서 불필요한 복사

// ❌ 문제: 10만 개 std::string 복사 → 메모리·CPU 낭비
std::vector<std::string> logs(100000);
for (std::string log : logs) {
    process(log);  // 매 반복마다 문자열 복사
}

// ✅ 해결: const 참조
for (const auto& log : logs) {
    process(log);  // 복사 없음
}

시나리오 4: 순회 중 컨테이너 수정으로 크래시

// ❌ 문제: 반복자 무효화
std::vector<int> vec = {1, 2, 3, 4, 5};
for (int num : vec) {
    if (num % 2 == 0) {
        vec.erase(std::find(vec.begin(), vec.end(), num));  // UB!
    }
}

// ✅ 해결: erase-remove 관용구
vec.erase(std::remove_if(vec.begin(), vec.end(),
     { return n % 2 == 0; }), vec.end());

목차

  1. 범위 기반 for 기초
  2. 참조와 const
  3. 구조화된 바인딩 (C++17)
  4. 반복자 요구사항
  5. 커스텀 타입 지원
  6. 흔한 실수와 해결법
  7. 성능 최적화 팁
  8. 실전 패턴
  9. 프로덕션 패턴

1. 범위 기반 for 기초

기본 문법

for (int num : vec)는 “vec의 각 요소를 num에 복사해서 루프”라는 뜻입니다. 반복자(begin/end)를 직접 쓰지 않아도 되고, 타입은 autoint처럼 요소 타입만 적으면 됩니다. 수정이 필요 없으면 const auto&를 쓰면 복사 없이 참조로 순회할 수 있어 큰 컨테이너나 문자열 요소에서 유리합니다.

std::vector<int> vec = {1, 2, 3, 4, 5};

// 범위 기반 for
for (int num : vec) {
    std::cout << num << " ";
}
// 1 2 3 4 5

동작 원리: 범위 기반 for는 컴파일 시 위와 같은 전통적인 for문으로 치환됩니다. begin()/end()를 한 번만 호출하고, 매 반복마다 *it로 현재 요소를 가져옵니다. 따라서 순회 중에 컨테이너를 수정해 반복자가 무효화되면 안 되고, “처음부터 끝까지 한 바퀴” 돌 때만 사용하는 것이 안전합니다.

// 컴파일러가 이렇게 변환
for (auto it = vec.begin(); it != vec.end(); ++it) {
    int num = *it;
    std::cout << num << " ";
}

다양한 컨테이너

// vector
std::vector<std::string> names = {"Alice", "Bob", "Charlie"};
for (const std::string& name : names) {
    std::cout << name << "\n";
}

// array
int arr[] = {1, 2, 3, 4, 5};
for (int num : arr) {
    std::cout << num << " ";
}

// set
std::set<int> numbers = {5, 2, 8, 1};
for (int num : numbers) {
    std::cout << num << " ";  // 1 2 5 8 (정렬됨)
}

// map
std::map<std::string, int> ages = {{"Alice", 25}, {"Bob", 30}};
for (const auto& pair : ages) {
    std::cout << pair.first << ": " << pair.second << "\n";
}

코드 상세 설명:

vector 순회:

  • const std::string& name: 참조로 받아 복사를 피합니다. 문자열은 크기가 가변적이므로 복사 비용이 큽니다.
  • const를 붙여 실수로 원본을 수정하는 것을 방지합니다.

배열 순회:

  • int num: 기본 타입(int)은 크기가 작아서 복사해도 참조와 성능 차이가 거의 없습니다.
  • 컴파일러가 배열의 크기를 자동으로 인식합니다.

set 순회:

  • std::set은 자동으로 정렬되므로 삽입 순서와 관계없이 오름차순으로 순회됩니다.
  • 입력: {5, 2, 8, 1} → 출력: 1 2 5 8

map 순회:

  • const auto& pair: map의 요소는 std::pair<const Key, Value> 타입입니다.
  • pair.first: 키 (문자열)
  • pair.second: 값 (정수)
  • map도 키를 기준으로 자동 정렬됩니다.

초기화 리스트

// 임시 컨테이너
for (int num : {1, 2, 3, 4, 5}) {
    std::cout << num << " ";
}

// 문자열 리터럴
for (char c : "Hello") {
    std::cout << c << " ";
}
// H e l l o (null 포함)

코드 상세 설명:

임시 초기화 리스트:

  • {1, 2, 3, 4, 5}std::initializer_list<int>로 변환됩니다.
  • 컴파일러가 임시 객체를 만들어 순회합니다.
  • 루프가 끝나면 자동으로 소멸됩니다.
  • 주의: 임시 객체이므로 참조를 저장하면 안 됩니다.

문자열 리터럴:

  • "Hello"const char[6] 타입입니다 (null 문자 포함).
  • 각 문자를 순회하며, 마지막에 '\0' (null 문자)도 포함됩니다.
  • null 문자를 제외하려면:
std::string str = "Hello";
for (char c : str) { ... }  // null 제외

완전한 범위 기반 for 예제 (복사·실행 가능)

예제 1: 기본 순회 (컴파일·실행 가능)

// range_for_basic.cpp
// g++ -std=c++17 -o range_for_basic range_for_basic.cpp && ./range_for_basic
#include <iostream>
#include <vector>
#include <string>

int main() {
    std::vector<std::string> fruits = {"apple", "banana", "cherry"};

    std::cout << "=== 값으로 순회 (복사) ===\n";
    for (std::string fruit : fruits) {
        std::cout << fruit << " ";
    }
    std::cout << "\n";

    std::cout << "=== 참조로 순회 (권장) ===\n";
    for (const auto& fruit : fruits) {
        std::cout << fruit << " ";
    }
    std::cout << "\n";

    std::cout << "=== 수정 가능 참조 ===\n";
    std::vector<int> nums = {1, 2, 3};
    for (auto& n : nums) {
        n *= 2;
    }
    for (int n : nums) {
        std::cout << n << " ";  // 2 4 6
    }
    std::cout << "\n";

    return 0;
}

예제 2: map + 구조화된 바인딩

// range_for_map.cpp
// g++ -std=c++17 -o range_for_map range_for_map.cpp && ./range_for_map
#include <iostream>
#include <map>
#include <string>

int main() {
    std::map<std::string, int> inventory = {
        {"sword", 3},
        {"shield", 5},
        {"potion", 12}
    };

    std::cout << "=== 구조화된 바인딩 ===\n";
    for (const auto& [item, count] : inventory) {
        std::cout << item << ": " << count << "개\n";
    }

    std::cout << "=== 값만 사용 ===\n";
    for (const auto& [_, count] : inventory) {
        std::cout << count << " ";
    }
    std::cout << "\n";

    return 0;
}

예제 3: tuple·구조체 분해

// range_for_tuple.cpp
// g++ -std=c++17 -o range_for_tuple range_for_tuple.cpp && ./range_for_tuple
#include <iostream>
#include <vector>
#include <tuple>
#include <string>

struct Point {
    int x, y;
};

int main() {
    std::vector<std::tuple<std::string, int, double>> students = {
        {"Alice", 25, 95.5},
        {"Bob", 23, 87.3}
    };

    for (const auto& [name, age, score] : students) {
        std::cout << name << " (" << age << "): " << score << "\n";
    }

    std::vector<Point> points = {{1, 2}, {3, 4}, {5, 6}};
    for (const auto& [x, y] : points) {
        std::cout << "(" << x << ", " << y << ")\n";
    }

    return 0;
}

2. 참조와 const

복사 vs 참조

for (std::string name : names)처럼 값으로 받으면 매 반복마다 std::string이 복사됩니다. 요소가 많거나 문자열이 길면 불필요한 할당·복사가 반복되어 느려지므로, 읽기만 할 때는 const std::string& name 으로 참조로 받는 것이 좋습니다. 수정할 필요가 있으면 std::string& name을 쓰고, 원본을 바꾸지 않을 때는 const를 붙이는 습관이 안전합니다.

std::vector<std::string> names = {"Alice", "Bob", "Charlie"};

// ❌ 나쁜 예: 복사 발생 (느림)
for (std::string name : names) {
    std::cout << name << "\n";
}

// ✅ 좋은 예: 참조 사용 (빠름)
for (const std::string& name : names) {
    std::cout << name << "\n";
}

성능 비교: 요소 타입이 크면(구조체·클래스) 값으로 받을 때마다 복사 생성자가 호출됩니다. 10000개 요소를 1000-int 배열인 구조체로 순회하면, 값으로 받을 때는 10000번의 큰 복사가 발생하고, 참조로 받으면 포인터만 넘어가므로 비용이 거의 없습니다. 범위 기반 for를 쓸 때는 “요소를 복사할 필요가 있는가”를 먼저 생각하고, 없으면 const auto& 또는 auto&를 사용하는 것이 좋습니다.

struct LargeObject {
    int data[1000];
};

std::vector<LargeObject> objects(10000);

// 복사: 매우 느림
for (LargeObject obj : objects) {
    // 10000번 복사 발생
}

// 참조: 빠름
for (const LargeObject& obj : objects) {
    // 복사 없음
}

수정 가능한 참조

std::vector<int> numbers = {1, 2, 3, 4, 5};

// 값 수정
for (int& num : numbers) {
    num *= 2;
}
// numbers: {2, 4, 6, 8, 10}

// const 참조: 수정 불가
for (const int& num : numbers) {
    // num *= 2;  // ❌ 컴파일 에러
    std::cout << num << " ";
}

auto 사용

std::map<std::string, int> scores = {{"Alice", 95}, {"Bob", 87}};

// auto: 타입 자동 추론
for (auto pair : scores) {  // ❌ 복사
    std::cout << pair.first << "\n";
}

// const auto&: 참조 (권장)
for (const auto& pair : scores) {  // ✅ 복사 없음
    std::cout << pair.first << "\n";
}

// auto&: 수정 가능
std::vector<int> vec = {1, 2, 3};
for (auto& num : vec) {
    num *= 2;
}

3. 구조화된 바인딩 (C++17)

pair 분해

std::map<std::string, int> scores = {
    {"Alice", 95},
    {"Bob", 87},
    {"Charlie", 92}
};

// 이전 방식
for (const auto& pair : scores) {
    std::cout << pair.first << ": " << pair.second << "\n";
}

// ✅ C++17: 구조화된 바인딩
for (const auto& [name, score] : scores) {
    std::cout << name << ": " << score << "\n";
}

tuple 분해

std::vector<std::tuple<std::string, int, double>> students = {
    {"Alice", 25, 95.5},
    {"Bob", 23, 87.3},
    {"Charlie", 24, 92.1}
};

for (const auto& [name, age, score] : students) {
    std::cout << name << " (" << age << "): " << score << "\n";
}

구조체 분해

struct Point {
    int x;
    int y;
};

std::vector<Point> points = {{1, 2}, {3, 4}, {5, 6}};

for (const auto& [x, y] : points) {
    std::cout << "(" << x << ", " << y << ")\n";
}

배열 분해

int arr[][2] = {{1, 2}, {3, 4}, {5, 6}};

for (const auto& [a, b] : arr) {
    std::cout << a << ", " << b << "\n";
}

4. 범위 기반 for의 반복자 요구사항

범위 기반 for가 동작하려면 range가 다음 중 하나를 만족해야 합니다.

요구사항 1: begin/end 멤버 함수

class MyContainer {
    std::vector<int> data;
public:
    auto begin() { return data.begin(); }
    auto end()   { return data.end(); }
    auto begin() const { return data.begin(); }
    auto end()   const { return data.end(); }
};

// 사용
MyContainer c;
for (int x : c) { /* ... */ }

요구사항 2: begin(range)/end(range) 자유 함수

struct CustomRange {
    int* ptr;
    size_t len;
};

int* begin(CustomRange& r) { return r.ptr; }
int* end(CustomRange& r)   { return r.ptr + r.len; }

// 사용
int arr[] = {1, 2, 3};
CustomRange r{arr, 3};
for (int x : r) { /* ... */ }

반복자가 갖춰야 할 연산

연산설명
*it현재 요소 역참조
++it다음 요소로 이동
it != end끝 여부 비교

InputIterator 이상이면 됩니다. begin()end()의 반환 타입은 서로 비교 가능해야 합니다.

배열·초기화 리스트

  • 배열: std::begin(arr), std::end(arr) 사용 (C++11)
  • std::initializer_list: begin(), end() 멤버 제공
// 배열
int arr[] = {1, 2, 3};
for (int x : arr) { }  // OK

// 초기화 리스트
for (int x : {1, 2, 3}) { }  // OK

5. 커스텀 타입 지원

begin/end 제공

class Range {
    int start, end;

public:
    Range(int s, int e) : start(s), end(e) {}

    // 반복자 타입
    class Iterator {
        int current;
    public:
        Iterator(int val) : current(val) {}

        int operator*() const { return current; }
        Iterator& operator++() { ++current; return *this; }
        bool operator!=(const Iterator& other) const {
            return current != other.current;
        }
    };

    Iterator begin() const { return Iterator(start); }
    Iterator end() const { return Iterator(end); }
};

int main() {
    Range range(1, 6);

    for (int num : range) {
        std::cout << num << " ";  // 1 2 3 4 5
    }
}

간단한 컨테이너

template <typename T, size_t N>
class FixedArray {
    T data[N];

public:
    T* begin() { return data; }
    T* end() { return data + N; }

    const T* begin() const { return data; }
    const T* end() const { return data + N; }

    T& operator { return data[i]; }
};

int main() {
    FixedArray<int, 5> arr;
    arr[0] = 1;
    arr[1] = 2;
    arr[2] = 3;
    arr[3] = 4;
    arr[4] = 5;

    for (int num : arr) {
        std::cout << num << " ";
    }
}

6. 흔한 실수와 해결법

실수 1: 임시 객체에 대한 참조 저장 (댕글링 참조)

// ❌ 위험: getVector()의 반환값이 즉시 소멸
for (const auto& item : getVector()) {
    // item은 댕글링 참조! 미정의 동작
}

// ✅ 안전: 변수에 저장
auto vec = getVector();
for (const auto& item : vec) {
    // OK
}

실수 2: 순회 중 컨테이너 수정

// ❌ 위험: 반복자 무효화
std::vector<int> vec = {1, 2, 3, 4, 5};
for (int num : vec) {
    if (num % 2 == 0) {
        vec.erase(std::find(vec.begin(), vec.end(), num));  // UB
    }
}

// ✅ 안전: erase-remove 관용구
vec.erase(std::remove_if(vec.begin(), vec.end(),
     { return n % 2 == 0; }), vec.end());

// ✅ 또는 수집 후 일괄 삭제
std::vector<int> toRemove;
for (int num : vec) {
    if (num % 2 == 0) toRemove.push_back(num);
}
for (int n : toRemove) {
    vec.erase(std::find(vec.begin(), vec.end(), n));
}

실수 3: 문자열 리터럴의 null 문자 포함

// ❌ 의도와 다를 수 있음: null 문자까지 순회
for (char c : "Hello") {
    std::cout << (int)c << " ";  // 72 101 108 108 111 0
}

// ✅ null 제외
std::string s = "Hello";
for (char c : s) {
    std::cout << c << " ";
}

실수 4: map의 키 타입 수정 시도

std::map<std::string, int> m = {{"a", 1}};

// ❌ 컴파일 에러: map의 키는 const
for (auto& [key, value] : m) {
    key = "b";  // 에러: key는 const std::string&
}

// ✅ 값만 수정
for (auto& [key, value] : m) {
    value = 2;  // OK
}

실수 5: 구조화된 바인딩에서 참조 저장

std::map<std::string, int> m = {{"a", 1}};

// ❌ 위험: m이 소멸하면 ref는 댕글링
std::string* ref = nullptr;
for (const auto& [key, value] : m) {
    ref = &key;  // 루프 밖에서 사용 시 위험
}

// ✅ 안전: 루프 내에서만 사용하거나 복사
std::string keyCopy;
for (const auto& [key, value] : m) {
    keyCopy = key;
    // ...
}

7. 성능 최적화 팁

팁 1: 기본적으로 const auto& 사용

// ✅ 큰 객체·문자열: 참조 필수
for (const auto& item : largeContainer) {
    process(item);
}

// ✅ 작은 타입(int, char): 복사해도 무방
for (int n : smallInts) {
    sum += n;
}

팁 2: begin/end는 한 번만 호출됨

범위 기반 for는 begin()/end()를 루프 진입 시 한 번만 호출합니다. 따라서 getRange()처럼 비용이 있는 함수를 쓰더라도 한 번만 호출됩니다.

// begin/end 각 1회만 호출
for (const auto& x : getExpensiveRange()) {
    // ...
}

팁 3: reserve로 재할당 방지

순회만 할 때는 해당 없지만, 범위 기반 for로 결과를 채우는 벡터가 있다면 reserve로 재할당을 줄이세요.

std::vector<int> result;
result.reserve(source.size());
for (const auto& x : source) {
    result.push_back(transform(x));
}

팁 4: 루프 내 할당 최소화

// ❌ 매 반복마다 문자열 생성
for (const auto& item : items) {
    std::string msg = "Item: " + item;  // 할당 반복
}

// ✅ 루프 밖에서 재사용
std::string msg;
for (const auto& item : items) {
    msg = "Item: " + item;  // 재할당만 (reserve 활용 가능)
}

팁 5: C++20 ranges로 지연 연산

#include <ranges>

// 필터·변환을 지연 평가
for (int x : numbers | std::views::filter( { return n % 2 == 0; })
                    | std::views::transform( { return n * n; })) {
    std::cout << x << " ";
}

8. 실전 패턴

패턴 1: 인덱스와 함께 순회

std::vector<std::string> names = {"Alice", "Bob", "Charlie"};

// C++20 이전
size_t index = 0;
for (const auto& name : names) {
    std::cout << index << ": " << name << "\n";
    ++index;
}

// 또는 전통적 for
for (size_t i = 0; i < names.size(); ++i) {
    std::cout << i << ": " << names[i] << "\n";
}

패턴 2: 역순 순회

std::vector<int> numbers = {1, 2, 3, 4, 5};

// 역순 반복자
for (auto it = numbers.rbegin(); it != numbers.rend(); ++it) {
    std::cout << *it << " ";  // 5 4 3 2 1
}

// C++20: std::views::reverse
#include <ranges>
for (int num : numbers | std::views::reverse) {
    std::cout << num << " ";  // 5 4 3 2 1
}

패턴 3: 필터링

std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

// 짝수만 출력
for (int num : numbers) {
    if (num % 2 == 0) {
        std::cout << num << " ";  // 2 4 6 8 10
    }
}

// C++20: std::views::filter
for (int num : numbers | std::views::filter( { return x % 2 == 0; })) {
    std::cout << num << " ";
}

패턴 4: 변환

std::vector<int> numbers = {1, 2, 3, 4, 5};

// 제곱 출력
for (int num : numbers) {
    std::cout << num * num << " ";  // 1 4 9 16 25
}

// C++20: std::views::transform
for (int square : numbers | std::views::transform( { return x * x; })) {
    std::cout << square << " ";
}

패턴 5: 중첩 컨테이너

std::vector<std::vector<int>> matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

// 2차원 순회
for (const auto& row : matrix) {
    for (int val : row) {
        std::cout << val << " ";
    }
    std::cout << "\n";
}

패턴 6: map 키/값 분리

std::map<std::string, int> data = {{"a", 1}, {"b", 2}, {"c", 3}};

// 키만
for (const auto& [key, _] : data) {
    std::cout << key << " ";
}

// 값만
for (const auto& [_, value] : data) {
    std::cout << value << " ";
}

패턴 7: 조기 종료

std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

// 5보다 큰 첫 번째 값 찾기
for (int num : numbers) {
    if (num > 5) {
        std::cout << "Found: " << num << "\n";
        break;
    }
}

// 짝수 건너뛰기
for (int num : numbers) {
    if (num % 2 == 0) continue;
    std::cout << num << " ";  // 1 3 5 7 9
}

9. 프로덕션 패턴

패턴 1: 로깅·모니터링 루프

void logConfig(const std::map<std::string, std::string>& config) {
    for (const auto& [key, value] : config) {
        spdlog::info("config[{}] = {}", key, value);
    }
}

패턴 2: JSON/설정 파싱 결과 순회

// nlohmann::json 등 사용 시
void processUsers(const json& users) {
    for (const auto& user : users) {
        std::string name = user["name"];
        int age = user["age"];
        // ...
    }
}

패턴 3: 에러 수집 후 일괄 처리

std::vector<std::string> collectErrors(const std::vector<Record>& records) {
    std::vector<std::string> errors;
    errors.reserve(records.size());
    for (const auto& rec : records) {
        if (auto err = validate(rec)) {
            errors.push_back(*err);
        }
    }
    return errors;
}

패턴 4: 스레드 풀 작업 분배

void processBatch(const std::vector<Task>& tasks) {
    std::vector<std::future<Result>> futures;
    futures.reserve(tasks.size());
    for (const auto& task : tasks) {
        futures.push_back(std::async(std::launch::async, process, task));
    }
    for (auto& f : futures) {
        handleResult(f.get());
    }
}

패턴 5: 안전한 순회 (컨테이너 복사본)

// 원본 수정이 필요할 때 복사본 순회
void removeExpired(std::map<std::string, CacheEntry>& cache) {
    auto copy = cache;  // 복사
    for (const auto& [key, entry] : copy) {
        if (entry.isExpired()) {
            cache.erase(key);  // 원본에서 삭제
        }
    }
}

주의사항

임시 객체 주의

// ❌ 위험: 임시 객체의 수명
for (const auto& item : getVector()) {
    // getVector()의 반환값이 즉시 소멸
    // item은 댕글링 참조!
}

// ✅ 안전: 변수에 저장
auto vec = getVector();
for (const auto& item : vec) {
    // OK
}

컨테이너 수정 금지

std::vector<int> vec = {1, 2, 3, 4, 5};

// ❌ 위험: 순회 중 컨테이너 수정
for (int num : vec) {
    if (num % 2 == 0) {
        vec.erase(/* ... */);  // 반복자 무효화!
    }
}

// ✅ 안전: 별도 컨테이너 사용
std::vector<int> toRemove;
for (int num : vec) {
    if (num % 2 == 0) {
        toRemove.push_back(num);
    }
}

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

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

  • C++ auto와 decltype | 타입 추론으로 코드 간결하게 만드는 방법
  • C++ 가변 인자 템플릿 | Variadic Templates와 Fold Expression

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

C++ 범위 기반 for, range-based for, 구조화된 바인딩, auto key value, map 순회, tuple pair 등으로 검색하시면 이 글이 도움이 됩니다.

정리

항목사용법
기본for (auto item : container)
참조for (auto& item : container)
const 참조for (const auto& item : container)
구조화된 바인딩for (const auto& [k, v] : map)
수정for (auto& item : container)
읽기 전용for (const auto& item : container)

핵심 원칙:

  1. 기본적으로 const auto& 사용
  2. 수정 필요 시 auto&
  3. 작은 타입(int 등)은 복사 OK
  4. 큰 객체는 항상 참조
  5. map은 구조화된 바인딩 활용

자주 묻는 질문 (FAQ)

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

A. C++11 범위 기반 for문과 C++17 구조화된 바인딩 완벽 가이드. for(auto& item : container) 문법, const·참조 선택, map 순회 시 auto [key, value], 구조화된 바… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

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

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

Q. 더 깊이 공부하려면?

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

한 줄 요약: 범위 기반 for와 구조화된 바인딩으로 반복문을 짧고 안전하게 쓸 수 있습니다. 다음으로 optional·variant·any(#12-3)를 읽어보면 좋습니다.

이전 글: C++ 실전 가이드 #12-1: auto와 decltype

다음 글: C++ 실전 가이드 #12-3: optional, variant, any

아키텍처 다이어그램

graph TD
    A[시작] --> B{조건 확인}
    B -->|예| C[처리 1]
    B -->|아니오| D[처리 2]
    C --> E[완료]
    D --> E

설명: 위 다이어그램은 전체 흐름을 보여줍니다.


관련 글

  • C++ optional·variant·any |
  • C++ auto와 decltype | 타입 추론으로 코드 간결하게 만드는 방법
  • C++ 참조(Reference) 완벽 가이드 | lvalue·rvalue
  • C++ 범위 기반 for문 에러 |
  • C++ 파일 입출력 | ifstream·ofstream으로