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 기초
기본 문법
for (int num : vec)는 “vec의 각 요소를 num에 복사해서 루프”라는 뜻입니다. 반복자(begin/end)를 직접 쓰지 않아도 되고, 타입은 auto나 int처럼 요소 타입만 적으면 됩니다. 수정이 필요 없으면 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) |
핵심 원칙:
- 기본적으로
const auto&사용 - 수정 필요 시
auto& - 작은 타입(int 등)은 복사 OK
- 큰 객체는 항상 참조
- 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으로