auto가 원치 않는 형식으로 연역되는 예가 있다.
std::vector<bool> features(const Widget& w);
Widget w;
bool highPriority = features(w)[5];
processWidget(w, highPriority);
위 코드에서는 정상적으로 작동한다. 하지만 highPriority를 auto로 대체하면 상황은 달라진다.
std::vector<bool>의 operator[]가 돌려주는 것은 한 요소에 대한 참조가 아니라 std::vector<bool>::reference 형식의 객체이다.
std::vector<bool>::reference가 존재하는 것은, std::vector<bool>이 자신의 bool들을 bool당 1비트의 압축된 형태로 표현하도록 명시되어 있기 때문이다. 그렇기 때문에 operator[]를 직접적으로 구현할 수 없다. std::vector<T>의 operator[]는 T&를 돌려주도록 되어있지만, C++에서 비트에 대한 참조는 금지되어 있기 때문에 std::vector<bool>의 operator[]는 bool&처럼 작동하는 객체를 돌려주는 우회책을 사용한다. 이를 가능하게 하는 것은 std::vector<bool>::reference의 기능중 하나가 bool로의 암묵적 변환이다.
bool highPriority = features(w)[5]; // 명시적으로 선언
여기서 features는 std::vector<bool>객체를 돌려주며 그 객체에 대해 operator[]가 호출된다.
auto highPriority = features(w)[5]; // highPriority의 형식을 연역한다.
features는 std::vector<bool> 객체를 돌려주며 operator[]가 호출되고 std::vector<bool>::reference 객체를 돌려주는 것까진 같다. 하지만 이후에 auto에 의해 highPriority의 형식이 연역되기 때문에 임시 std::vector<bool> 객체를 돌려준다. 이 객체는 std::vector<bool>::reference 객체의 한 복사본이며 임시 객체이므로 문장의 끝에서 파괴된다. 즉, processWidget(w, highPriority); 호출은 미정의 행동을 유발한다.
std::vector<bool>::reference는 대리자 클래스, 즉 다른 형식의 행동을 흉내내고 고방하는 것이 존재 이유인 클래스의 예이다.
표준 라이브러리 외에, 대리자 클래스는 표현식 템플릿 이라는 기법을 사용하는 C++라이브러리에서도 흔히 쓰인다.
정리하자면, 암묵적 변환을 지원하는 대리자 클래스의 표현식을 auto의 사용을 피해야 한다.
실제 응용에서 대리자 클래스의 존재를 발견하여 auto가 대리자 클래스의 형식을 연역함을 알게 되었다고 하자. auto는 우리가 원했던 것과 다른 형식을 연역하기 때문에 명시적으로 지정한 초기치 관용구를 사용해 해결 할 수 있다.
형식 명시 초기치 관용구에서는 변수를 auto로 선언하되, 초기화 표현식의 형식을 auto가 연역하길 원하는 형식으로 캐스팅 한다.
auto highPriority = static_cast<bool>(features(w)[5]);
features(w)[5]는 std::vector<bool>::reference 객체를 돌려주지만 캐스팅 때문에 표현식의 형식은 bool이 되며, 결과적으로 auto는 highPriority의 형식을 bool로 연역한다. 실행 시점에서 std::vector<bool>::operator[]가 돌려준 std::vector<bool>::reference 객체는 자신이 지원하는 bool로의 변환을 수행하며, 그 변환 도중 features가 돌려준 std::vector<bool>을 가리키는 포인터가 역참조 된다. 그 시점에서 포인터는 여전히 유효하므로, 이전에 겪었던 미정의 행동 문제는 발생하지 않는다. 다음으로, 역참조된 bool vector에 대해 색인 5가 적용되며, 결국 highPriority가 5번 비트의 bool 값으로 초기화 된다.
Conclusion
- "보이지 않는" 대리자 형식 때문에 auto가 포기화 표현식의 형식을 "잘못" 연역할 수 있다.
- 형식 명시 초기치 관용구는 auto가 원하는 형식을 연역하도록 강제한다.
Reference
Effective Modern C++ 항목 6: auto가 원치 않은 형식으로 연역될 때에는 명시적 형식의 초기치를 사용하라
'C++ > Effective Modern C++' 카테고리의 다른 글
0과 NULL 보다 nullptr (0) | 2020.06.30 |
---|---|
객체 생성시 괄호와 중괄호의 구분 (0) | 2020.06.29 |
명시적 형식 선언보다는 auto를 선호하라 (0) | 2020.06.27 |
연역된 형식을 파악하는 방법 (0) | 2020.06.26 |
decltype의 작동 방식 (0) | 2020.06.26 |
댓글