Abseil Tip 232 변수 선언 시 auto를 언제 사용할 것인가

아래는 “이번 주의 팁 #232: 변수 선언 시 auto를 언제 사용할 것인가”에 대한 한글 번역입니다.


제목: “이번 주의 팁 #232: 변수 선언 시 auto를 언제 사용할 것인가”

원문 게시일: 2024년 6월 20일
업데이트: 2024년 9월 30일

작성자: Kenji Inoue, Michael Diamond

빠른 링크: abseil.io/tips/232


개요

Google의 스타일 가이드는 타입 추론(auto) 사용에 대해 이렇게 권장합니다:

타입 추론은 코드가 프로젝트에 익숙하지 않은 독자들에게 더 명확해지거나, 코드의 안전성을 향상시키는 경우에만 사용하세요. 단순히 타입을 명시하는 것이 번거롭다는 이유로 사용하지 마세요.

그러나 auto를 과도하게 사용하면 코드가 오히려 덜 명확해질 수 있습니다.
하지만 다음과 같은 경우 auto를 사용하면 코드의 가독성과 안전성을 높일 수 있습니다:

  1. 타입을 정확히 명시하기 어렵고, 잘못된 타입을 사용하면 성능 또는 정확성 문제가 발생할 수 있는 경우 (예: 맵에서 범위 기반 for 루프).
  2. 타입 정보가 지나치게 중복되며, 명시적으로 적는 것이 오히려 방해가 되는 경우 (예: 템플릿 팩토리 함수 및 반복자).
  3. 타입 자체가 중요하지 않고, 코드가 구문적으로 올바르면 되는 제너릭 상황.

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_typestd::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();

권장 사항 요약

  1. 타입 명시가 성능 또는 정확성 문제를 초래할 가능성이 높은 경우 auto를 사용하세요.
  2. 타입 정보가 지역 문맥에서 명확한 경우, auto를 사용하여 중복을 줄이세요.
  3. 타입을 명시하기 어렵거나 불가능한 제너릭 코드에서만 auto를 사용하세요.
  4. 그 외의 상황에서는 auto 사용을 피하세요.

이 원칙을 따르면 코드의 가독성과 유지보수성이 향상될 것입니다.


추가 참고자료