Menu

C++ 범위 기반 for 루프: 문법, auto, 참조

C++ 범위 기반 for 루프 설명 - 배열, vector, string, map을 깔끔하게 순회하는 방법, auto&와 const auto&를 써야 하는 이유, 그리고 피해야 할 복사와 이터레이터 무효화 함정.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

"각 요소에 대해 이것을 하라"

전통적인 카운터 방식 for 루프는 구동할 인덱스가 있을 때 훌륭합니다. 하지만 대부분의 경우 인덱스 자체에는 관심이 없고, 그저 컨테이너의 모든 요소를 다루고 싶을 뿐입니다. 그걸 위해 for (int i = 0; i < v.size(); i++)를 쓰는 것은 번거롭고, i는 단 하나의 off-by-one 실수로 범위를 벗어나 읽기 직전 상태입니다.

C++11은 바로 이것을 위해 범위 기반 for 루프를 추가했습니다. 변수에 이름을 붙이고 컨테이너를 가리키면, 루프가 모든 요소를 대신 순회해 줍니다.

인덱스도, .size()도, 틀리기 쉬운 경계도 없습니다. "scores 안의 각 s에 대해"라고 읽으세요. 원시 배열, std::vector, std::string, std::map, 그리고 begin()end()를 노출하는 모든 것에서 동작합니다.

타입은 auto에게 맡기기

요소 타입을 직접 적는 것은 동작하지만 취약합니다. 컨테이너의 타입을 바꾸면 모든 루프도 바뀌어야 합니다. 범위 기반 forauto와 짝지으면 컴파일러가 요소 타입을 대신 추론해 줍니다.

하지만 여기에는 숨은 비용이 있습니다. 평범한 auto namestring을 추론하고 매 순회마다 각 요소를 name으로 복사합니다. int라면 공짜이지만, string이나 큰 구조체라면 매 반복마다 낭비되는 할당입니다. 해결책은 참조이며, 그것이 다음으로 이해해야 할 내용입니다.

auto&로 제자리에서 수정하기

auto x라고 쓰면 복사본을 얻으므로, x에 대입하면 컨테이너가 아니라 복사본이 바뀝니다. 이 함정을 보세요.

n은 버려지는 복사본이기 때문에 10배 곱하기는 조용히 아무것도 하지 않습니다. 실제로 요소를 편집하려면 auto&참조에 의해 받으세요.

단 하나의 &가 "보기만 하고 건드리지 않기"와 "제자리에서 편집하기"의 모든 차이입니다. 변경 사항이 사라지는 이유가 궁금하다면 거의 항상 이것이 원인입니다.

복사 없이 읽기: const auto&

요소를 읽기만 하면 되는데 복사 비용이 클 때는 const auto&를 사용하세요. 참조는 복사를 피하고, const는 아무것도 수정하지 않을 것임을 문서화(하고 강제)합니다.

좋은 경험 법칙은 이렇습니다.

for (auto x : c)         // 복사 - 저렴한 타입 (int, char, 포인터)
for (auto& x : c)        // 편집 - 요소를 바꾸고 싶을 때
for (const auto& x : c)  // 읽기 - 살펴보기만 하는 무거운 타입

읽을 때는 기본적으로 const auto&, 쓸 때는 auto&를 사용하세요. 평범한 auto는 정말로 작고 복사가 저렴한 타입에만 쓰세요.

map과 pair 순회하기

std::map에 대한 범위 기반 for는 각 항목마다 .first(키)와 .second(값)를 가진 std::pair를 건네줍니다. C++17부터는 구조적 바인딩으로 그 pair를 루프 헤더에서 바로 이름 있는 두 변수로 풀어낼 수 있습니다.

[name, age]는 곳곳에서 entry.firstentry.second를 반복하는 것보다 훨씬 명확합니다. 여기서도 const auto&를 유지하세요. map 항목의 키는 string이므로 각 pair를 복사하면 낭비가 됩니다.

함정: 순회 중에 크기를 바꾸지 말 것

가장 큰 함정은 범위 기반 for가 컨테이너를 순회하는 동안 컨테이너의 크기를 바꾸는 것입니다. push_back, erase, insert, clear를 호출하면 내부 저장소가 재할당되어 루프 내부 이터레이터가 무효화될 수 있습니다. 그 결과는 정의되지 않은 동작, 즉 친절한 오류가 아니라 충돌이나 쓰레기 데이터입니다.

vector<int> v = {1, 2, 3};
for (int x : v) {
    v.push_back(x);   // 정의되지 않은 동작 - 재할당이 범위를 무효화한다
}

처리 중에 요소를 추가하거나 제거해야 한다면 인덱스 기반 또는 이터레이터 기반 for 루프로 바꾸고 경계를 직접 관리하거나, 별도의 결과 컨테이너를 만들어 나중에 교체하세요. 같은 부류의 더 작은 함정 두 가지: 범위 기반 for를 즉시 소멸하는 임시 객체에 절대 바인딩하지 마세요(for (auto x : makeVector())는 괜찮지만 for (auto& x : someObj.getTempVector())는 댕글링될 수 있습니다). 그리고 for (auto& c : myString)을 쓰면 개별 문자를 제자리에서 변경할 수 있다는 점을 기억하세요.

다음: 함수

범위 기반 for 루프는 순회를 깔끔하게 정리해 주며, 방금 배운 auto / auto& / const auto& 선택은 C++에서 가장 중요한 도구 중 하나로 그대로 이어집니다. 다음으로는 로직을 재사용 가능한 함수로 묶어 보겠습니다. 코드에 이름, 매개변수, 반환값을 부여해 스스로 반복하는 대신 어디서든 호출할 수 있게 만듭니다.

자주 묻는 질문

C++에서 범위 기반 for 루프란 무엇인가요?

범위 기반 for 루프는 인덱스나 이터레이터를 직접 관리하지 않고도 컨테이너(배열, vector, string, map 등)의 모든 요소를 방문합니다. 문법은 for (auto x : container) { ... }입니다. C++11에서 추가되었으며 "각 요소에 대해 이것을 하라"를 표현하는 가장 깔끔한 방법입니다.

범위 기반 for 루프에서 auto 대신 auto&를 써야 할 때는 언제인가요?

요소를 제자리에서 수정하고 싶을 때는 auto& x를, 단지 읽기만 하지만 복사를 피하고 싶을 때(string, vector, 큰 객체에서 중요)는 const auto& x를 사용하세요. 평범한 auto x는 매 반복마다 복사를 만듭니다. int 같은 저렴한 타입에는 괜찮지만 그 외에는 낭비입니다.

C++에서 범위 기반 for 루프 안에서 vector의 크기를 바꿀 수 있나요?

아니요. 순회 중인 컨테이너에 push_back, erase, insert, clear를 호출하면 루프 내부 이터레이터가 무효화되며 이는 정의되지 않은 동작입니다. 충돌하거나 데이터를 조용히 손상시킬 수 있습니다. 루프 도중 요소를 추가하거나 제거해야 한다면 대신 인덱스 기반 또는 이터레이터 기반 for 루프를 사용하세요.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기