int x; // 초기화가 되지 않았다
typename std::iterator_traits<It>::value_type currValue = *b; // 코드가 너무 길다
/* closure type */ func = [](){ ...; }; // 명시적 타입 지정 불가
auto를 사용하지 않으면 위와같은 문제를 마주칠 수 있다.
auto x; // 컴파일 오류
auto currValue = *b; // 코드가 간결해졌다
auto func = [](){ ...; }; // 클로저의 타입 추론이 가능해졌다
그런데 auto를 사용하는것 만으로 위의 문제가 모조리 해결이 된다. auto는 초기값에 의해 타입 추론이 이루어지기 때문에 초기화가 반드시 필요하다.
여기서 한가지, 클로저의 타입은 auto가 아니라 std::function으로도 가능한게 아닌가?
물론 가능하다.
auto func = [](const std::unique_ptr<Widget>& p1, const std::unique_ptr<Widget>& p2) { return *p1 < *p2; };
/* 심지어 이건 C++14부터 매개변수마저 auto를 쓸수 있다 */
/* auto func = [](const auto& p1, const auto& p2) { return *p1 < *p2; }; */
std::function<bool(const std::unique_ptr<Widget>&, const std::unique_ptr<Widget>&)> func =
[](const std::unique_ptr<Widget>& p1, const std::unique_ptr<Widget>& p2) { return *p1 < *p2; };
/* 현기증이 난다 */
function의 템플릿 매개변수에 일일이 다 써넣을 자신이 있다면 말이다.
또한 auto로 선언된 클로저를 담는 변수는 해당 클로저에 딱 맞는 메모리 크기만 사용하지만, std::function은 최소 크기가 고정되어있다. 심지어 클로저의 크기가 커서 다 못담는다면 힙 메모리를 할당해서 클로저를 저장하게 된다.
게다가 인라이닝도 제한되고 구현의 세부사항 때문에 auto타입의 클로저보다 느리다.
auto는 초기화 누락 방지, 코드 간결화, 클로저 타입 추론뿐만 아니라 타입 단축과 관련된 문제도 피할 수 있다.
std::vector<int> v;
...
unsigned int sz = v.size();
문제없어 보이지만 사실 size의 공식적인 반환 타입은 std::vector<int>::size_type이다.
size_type이 size_t로 되어있고, size_t는 unsigned long long으로 재정의 되어있을 뿐이다. (64비트 VS2022 기준)
물론 unsigned long long은 unsigned int로 암시적 변환이 허용되기 때문에 실행 자체는 문제가 없을 뿐이다.
애초에 이런 문제는 auto 선언이면 만사해결이다.
std::unordered_map<std::string, int> m;
...
for (const std::pair<std::string, int>& p : m) {
&p; // 임시 객체의 요소를 가리키는 포인터
}
이 코드는 어떨까. 문제가 없을까?
당연히 문제가 있다. 저장된 키값은 const이기 때문에 std::string이 아니라 const std::string이어야 한다.
분명히 pair를 참조자로 선언했음에도 const 객체를 비const 객체로 어떻게든 변환시키기 위해서 임시 객체를 생성하게 되고, 그 객체를 참조하게 된다. 원래 의도했던 참조자의 의미가 없어지는 순간이다. 생성된 임시 객체는 루프 끝에서 파괴되기 때문에 루프만큼 복사-파괴가 일어나는 문제가 발생한다.
std::unordered_map<std::string, int> m;
...
for (const auto& p : m) {
&p; // m의 요소를 가리키는 포인터
}
이 역시 auto 선언이면 골치 아플일이 없다.
auto가 마치 만병통치약인 것처럼 썼지만 결국 초기값의 타입으로부터 추론되는 것이기 때문에 초기값의 타입이 기대하지 않은 타입이거나 바람직하지 않은 타입일수도 있다. 그런 부분은 주의해서 사용하고 마지막으로 언급할 내용은 코드 가독성에 대한 얘기이다.
auto는 필수가 아닌 선택이다. 위에서 언급한 문제점들에 해당하더라도 명시적 선언이 더 깔끔하거나 유지보수에 용이하다면 명시적 선언을 사용하면 된다. 그래도 대체적으로는 auto 선언이 좋을것이다.
◾ auto 변수는 반드시 초기화해야 하며, 이식성 또는 효율성 문제를 유발할 수 있는 타입 불일치가 발생하는 경우가 거의 없으며, 대체로 변수의 타입을 명시적으로 지정할 때보다 타자량도 더 적다.
◾ auto로 타입을 지정한 변수는 항목 2와 항목 6에서 설명한 문제점들을 겪을 수 있다.
'도서 > Effective Modern C++' 카테고리의 다른 글
[3장] 항목 7 : 객체 생성 시 괄호와 중괄호를 구분하라 (0) | 2022.12.27 |
---|---|
[2장] 항목 6 : auto가 원치 않은 타입으로 추론될 때에는 명시적 타입의 초기값을 사용하라 (0) | 2022.12.26 |
[1장] 항목 4 : 추론된 타입을 파악하는 방법을 알아두라 (0) | 2022.12.24 |
[1장] 항목 3 : decltype의 작동 방식을 숙지하라 (0) | 2022.12.23 |
[1장] 항목 2 : auto의 타입 추론 규칙을 숙지하라 (0) | 2022.12.23 |