아래는 “이번 주의 팁 #232: 변수 선언 시 auto
를 언제 사용할 것인가”에 대한 한글 번역입니다.
제목: “이번 주의 팁 #232: 변수 선언 시 auto
를 언제 사용할 것인가”
원문 게시일: 2024년 6월 20일
업데이트: 2024년 9월 30일
작성자: Kenji Inoue, Michael Diamond
빠른 링크: abseil.io/tips/232
개요
Google의 스타일 가이드는 타입 추론(auto
) 사용에 대해 이렇게 권장합니다:
타입 추론은 코드가 프로젝트에 익숙하지 않은 독자들에게 더 명확해지거나, 코드의 안전성을 향상시키는 경우에만 사용하세요. 단순히 타입을 명시하는 것이 번거롭다는 이유로 사용하지 마세요.
그러나 auto
를 과도하게 사용하면 코드가 오히려 덜 명확해질 수 있습니다.
하지만 다음과 같은 경우 auto
를 사용하면 코드의 가독성과 안전성을 높일 수 있습니다:
- 타입을 정확히 명시하기 어렵고, 잘못된 타입을 사용하면 성능 또는 정확성 문제가 발생할 수 있는 경우 (예: 맵에서 범위 기반
for
루프). - 타입 정보가 지나치게 중복되며, 명시적으로 적는 것이 오히려 방해가 되는 경우 (예: 템플릿 팩토리 함수 및 반복자).
- 타입 자체가 중요하지 않고, 코드가 구문적으로 올바르면 되는 제너릭 상황.
1. 맵에서의 범위 기반 for
루프
다음 코드는 각 맵 요소를 의도치 않게 복사합니다:
absl::flat_hash_map<std::string, DogBreed> dog_breeds_by_name = ...;
// name_and_breed가 맵의 각 요소를 복사합니다.
for (const std::pair<std::string, DogBreed>& name_and_breed : dog_breeds_by_name) {
...
}
이 문제는 맵의 value_type
이 std::pair<const Key, Value>
로 정의되어 있기 때문입니다.
std::pair
는 암시적 변환을 허용하므로, const std::string
에서 std::string
으로의 변환이 발생하여 복사가 이루어집니다.
auto
를 사용하면 코드의 안전성과 성능을 모두 개선할 수 있습니다:
absl::flat_hash_map<std::string, DogBreed> dog_breeds_by_name = ...;
// 구조적 바인딩과 `auto` 사용
for (const auto& [name, breed] : dog_breeds_by_name) {
...
}
만약 요소의 타입이 지역 문맥에서 명확하지 않은 경우:
// 구조적 바인딩 없이 `auto` 사용
for (const auto& name_and_breed : dog_breeds_by_name) {
const std::string& name = name_and_breed.first;
const DogBreed& breed = name_and_breed.second;
...
}
2. 반복자
반복자 타입 이름은 길고, 컨테이너 타입이 코드에 명확히 나타나 있는 경우에는 중복되는 정보가 될 수 있습니다.
다음과 같은 반복자 선언 코드를 살펴보겠습니다:
std::vector<std::string> names = ...;
std::vector<std::string>::iterator name_it = names.begin();
while (name_it != names.end()) {
...
}
여기서 auto
를 사용하면 반복자 타입을 간결하게 표현할 수 있습니다:
std::vector<std::string> names = ...;
auto name_it = names.begin();
while (name_it != names.end()) {
...
}
단, 컨테이너 타입이 명확하지 않은 경우에는 전체 타입을 명시하는 것이 좋습니다:
std::vector<std::string>::iterator name_it = names_.begin();
while (name_it != names_.end()) {
...
}
또는 다음처럼 요소 타입을 명시할 수도 있습니다:
auto name_it = names_.begin();
while (name_it != names_.end()) {
const std::string& name = *name_it;
...
}
3. std::make_unique
및 기타 팩토리 함수
std::make_unique
와 같은 팩토리 함수는 반환 타입을 암시적으로 제공합니다.
예를 들어:
std::unique_ptr<MyFavoriteType> my_type =
std::make_unique<MyFavoriteType>(...);
proto2::ArenaSafeUniquePtr<MyFavoriteProto> my_proto =
proto2::MakeArenaSafeUnique<MyFavoriteProto>(arena);
여기서 auto
를 사용하면 코드의 중복을 줄이고 가독성을 높일 수 있습니다:
auto my_type = std::make_unique<MyFavoriteType>(...);
auto my_proto = proto2::MakeArenaSafeUnique<MyFavoriteProto>(arena);
4. 제너릭 코드
템플릿 코드나 GoogleTest 매처와 같은 제너릭 상황에서 타입을 명시하기 어려운 경우 auto
를 사용할 수 있습니다.
예를 들어, 템플릿 메타프로그래밍이나 decltype
을 활용한 복잡한 타입의 경우입니다.
그러나 이러한 상황은 드물게 발생하며, 신중히 사용해야 합니다.
5. auto
를 피해야 하는 경우
auto
를 사용할 때, 타입이 분명하다고 느껴질 수 있지만, 프로젝트에 익숙하지 않은 미래의 독자에게는 그렇지 않을 수 있습니다.
다음과 같은 패턴에서는 auto
사용을 피하세요:
// 잘못된 예: 명확하지 않은 `auto` 사용
const auto& breed = cat.pedigree().detailed_breed(); // breed의 타입이 불명확
위 코드는 타입 정보를 숨기므로, 다음처럼 명시적으로 작성하는 것이 더 낫습니다:
// 명확한 타입 명시
const DetailedDomesticCatBreed& breed = cat.pedigree().detailed_breed();
auto
는 복사 여부와 같은 중요한 의미도 숨길 수 있습니다:
// 복사가 발생하는지 알기 어렵다.
auto breed = cat.pedigree().detailed_breed();
명시적으로 작성하면 복사 여부를 쉽게 확인할 수 있습니다:
// 복사가 발생하지 않음을 명확히 표현
const DetailedDomesticCatBreed& breed = cat.pedigree().detailed_breed();
권장 사항 요약
- 타입 명시가 성능 또는 정확성 문제를 초래할 가능성이 높은 경우
auto
를 사용하세요. - 타입 정보가 지역 문맥에서 명확한 경우,
auto
를 사용하여 중복을 줄이세요. - 타입을 명시하기 어렵거나 불가능한 제너릭 코드에서만
auto
를 사용하세요. - 그 외의 상황에서는
auto
사용을 피하세요.
이 원칙을 따르면 코드의 가독성과 유지보수성이 향상될 것입니다.
추가 참고자료
- Google C++ 스타일 가이드:
auto
- Tip #4: 자동화를 위한
auto
- Tip #44:
auto
의 수식 지정