[EMC++] Item 6. auto가 원치 않는 타입으로 추론될 때에는 명시적 타입의 초기화를 사용해라
Item 6. auto가 원치 않는 타입으로 추론될 때에는 명시적 타입의 초기화를 사용해라
auto가 원치 않는 타입으로 추론되는 경우
auto
가 내가 생각한 타입과 다른 타입으로 추론되는 경우들이 존재한다.
간단한 예시로 std::vector<bool>
컨테이너에서 []
(인덱스 연산자)를 사용하는 경우가 있다.
std::vector<bool> v = {true, false, true};
auto b = v[2]; // b의 타입은 bool이 아닌 std::vector<bool>::reference 이다.
std::vector<T>
의 operator[]
는 T&
를 반환한다고 생각해서 auto
가 bool
타입을 추론할 것이라 기대했다면 상당히 당황스러울 수 있다.
auto
가 추론한 타입이 bool
이 아니라 std::vector<bool>::reference
인 이유는 std::vector<bool>
컨테이너가 내부에서 bool
을 1bit의 압축된 형태로 표현하기 때문이다.
C++에서는 비트에 대한 참조는 금지되어 있어 bool&
로 반환할 수 없기에 마치 bool&
처럼 동작하는 std::vector<bool>::reference
를 사용한다.
std::vector<bool>::reference
는 bool
타입으로 암시적 변환이 가능하다. 즉, 타입을 명시해 줄 경우 우리가 의도한 타입으로 사용할 수 있다.
std::vector<bool> features(const Widget& w);
Widget w;
// std::vector<bool>::reference에서 bool로 암시적 변환
bool highPriority = features(w)[5]
processWidget(w, highPriority);
그러면 원치 않는 타입으로 추론된 auto
를 사용할 경우 어떤 문제가 있을까?
// highPriority가 std::vector<bool>::reference 타입으로 추론
auto highPriority = features(w)[5]
// hightPriority는 파괴된 메모리를 가리키는 허상 포인터(dangling pointer)임
processWidget(w, highPriority);
std::vector<bool>::reference
는 std::vector<bool>
컨테이너 내부의 bool
데이터를 가리키고 있는 객체이다. (bit pointer와 offset을 가지고 있음)
만약 features(w)[5]
와 같이 문장 끝에서 파괴되는 임시 객체를 가리키고 있을 경우, 다음 문장에서 std::vector<bool>::reference
는 파괴된 메모리를 가리키는 허상 포인터(dangling pointer)가 된다.
auto와 대리자 클래스(Proxy Class)의 관계
std::vector<bool>::reference
처럼 특정한 타입의 행동을 흉내 내고 보강하는 클래스를 대리자 클래스(proxy class)라고 불린다.
proxy class는 여러 목적에 맞게 사용된다.
비트에 대한 참조를 제공하는
std::vector<bool>::reference
, 포인터의 기능을 보강하는 스마트 포인터 등등
이러한 proxy class들이 외부에서 잘 보여지지 않는 구현일 경우 auto
와 만났을 때 원치 않는 타입으로 추론되는 문제를 일으킨다.
C++ 라이브러리에서는 표현식 템플릿(expression template) 기법을 사용하는 proxy class들이 많은데 여기서도 추론 문제가 발생할 수 있다.
// Matrix 타입
Matrix sum = m1 + m2 + m3 + m4;
// Sum<Sum<Sum<Matrix, Matrix>, Matrix>, Matrix> 타입
auto sum = m1 + m2 + m3 + m4;
우변의 표현식을 효율적으로 계산하기 위해서 operator+
는 Matrix
객체 타입을 반환하지 않고 Sum<Matrix, Matrix>
와 같은 proxy class 타입을 반환한다.
어차피 계산이 끝나면 Matrix
타입으로 초기화할 수 있도록 암시적 형변환이 일어나지만 만약 변수 타입을 auto
로 지정할 경우 Sum<Sum<Sum<Matrix, Matrix>, Matrix>, Matrix>
타입으로 추론이 되어 버린다.
즉, auto
는 보이지 않는 proxy class 타입의 표현식을 추론할 때 문제가 발생한다.
이런 문제를 해결하기 위해서 명시적 타입의 초기화를 사용하는 방법이 있다.
auto를 추론하기 위해 명시적 타입의 초기화를 사용하는 방법
명시적 타입의 초기화(explicitly typed initializer idiom)는 명시적 형변환 연산자인 static_cast
를 사용해서 auto
가 원하는 타입으로 추론하도록 강제하는 것이다.
이 방법을 사용하면 앞서 auto
가 원치 않는 타입으로 추론되는 것을 해결할 수 있다.
std::vector<bool> features(const Widget& w);
Widget w;
// std::vector<bool>::reference 타입에서 bool 타입으로 명시적 변환
auto highPriority = static_cast<bool>(features(w)[5]);
// Sum<Sum<Sum<Matrix, Matrix>, Matrix>, Matrix> 타입에서 Matrix 타입으로 명시적 변환
auto sum = static_cast<Matrix>(m1 + m2 + m3 + m4);
static_cast
를 사용하면 의도하고자 하는 타입을 코드에서 명확하게 표현할 수 있는 장점도 있다.