스토리텔링 개발자

[Effective Modern C++] 6. auto의 타입 추론 실패 본문

개발/Effective Modern C++

[Effective Modern C++] 6. auto의 타입 추론 실패

김디트 2025. 2. 12. 10:58
728x90

항목 6. auto가 원치 않은 타입으로 추론될 때에는 명시적 타입의 초기치를 사용하라

 

 

 

auto 타입 추론 실패 상황
std::vector<bool> features(const Widget& w);

Widget w;
...

// 기존 처리
bool highPriority = features(w)[5];
processWidget(w, highPriority);

// auto를 사용한다면..
// 컴파일은 여전히 성공한다.
auto highPriority = features(w)[5];
processWidget(w, highPriority); // 미정의 행동!
  • auto 버전에서는 features(w)[5]의 타입이 bool이 아니기 때문이다.
    • std::vector<bool>의 operator[]가 리턴하는 것은 std::vector<bool>::reference 타입의 객체이다.
  • std::vector<bool>::reference?
    • std::vector<bool>은 bool들을 실제 bool이 아니라 1비트의 합축된 형태로 가진다.
    • 하지만 C++에서는 비트에 대한 참조가 금지되어 있다.
    • 즉, bool&처럼 작동하는 객체를 돌려주는 프록시 클래스 객체이다.
    • 이 객체는 bool로의 암시적 변환을 지원한다.
  • 미정의 행동을 하는 이유
    1. highPriority는 features 함수를 호출한다.
    2. features 함수는 std::vector<bool> 임시 객체를 돌려준다.
    3. 그 임시 벡터 객체로부터 std::vector<bool>::reference 객체를 리턴 받는다.
    4. 하지만, 이 모든 게 끝난 후에는 std::vector<bool> 임시 객체는 파괴된다.
    5. 결국, 리턴 받은 std::vector<bool>::reference 객체 내부에서 가리키는 std::vector<bool>에 대한 포인터는 댕글링 포인터가 된다.

 

 

 

 

프록시 클래스
  • 다른 타입의 행동을 흉내내고 보강하는 클래스 (MEC++ 항목 30 참조)
  • 클라이언트가 명확히 알 수 있게 설계된 것들
    • std::shared_ptr, std::unique_ptr
  • 다소 은밀하게 작동하도록 설계된 것들
    • std::vector<bool>::reference, 표현식 템플릿(expression template) 기법을 사용하는 라이브러리(Matrix 등)
    • 대체로 수명이 한 문장 이상일 거라고 가정하지 않고 설계되어 있다.
  • 대체로 은밀하게 작동하는 프록시 클래스는 auto와 잘 맞지 않는다.
  • 그러므로 다음과 같은 형태의 코드는 피하도록 해야 한다.
auto someVar = "보이지 않는" 프록시 클래스 타입의 표현식;

 

 

 

프록시 객체의 존재를 확인하는 방법
  • 라이브러리 문서를 확인한다.
  • 헤더 파일에서 프록시 객체의 흔적을 확인한다.
    • 대체로 프록시 객체는 클라이언트가 호출하도록 만들어진 어떤 함수가 돌려준다.
    • // std::vector<bool>::operator[]의 명세
      namespace std
      {
          template <class Alocator>
          class vector<bool, Allocator>
          {
              public:
                  ....
                  class reference { ... }; // 프록시 클래스
                  
                  reference operator[](size_type n); // 프록시 객체를 리턴함을 확인 가능하다.
                  ...
          };
      }

 

 

 

auto 추론 실패 해결법
  • auto를 버린다?
    • auto 자체는 문제가 아니므로 진정한 해법이 아니다.
  • auto가 다른 타입을 추론하도록 강제한다.
  • 타입 명시 초기치 관용구(explicitly typed initializer idiom)을 사용하여 해결.
    • 변수를 auto로 선언하되,
    • 초기화 표현식의 타입을 auto가 추론하길 원하는 타입으로 캐스팅한다.
auto highPriority = static_cast<bool>(features(w)[5]);

auto sum = static_cast<Matrix>(m1 + m2 + m3 + m4);
  • 물론 프록시 클래스 타입에만 적용할 수 있는 건 아니다.
    • 초기화에 쓰이는 표현식이 산출하는 타입과는 다른 타입으로 변수를 생성하고자 하는 의도를 명확히 할 때도 쓰인다.
// 1.
double calcEpsilon();

float ep = calcEpsilon(); // float로 암시적 변환이 발생.
// 하지만, 함수가 돌려준 값의 정밀도를 일부러 줄이고자 한다는 의도가 확실히 보이지 않는다.

auto ep = static_cast<float>(calcEpsilon()); // 타입 명시 초기치 관용구

// 2.
// 0.0 ~ 1.0 사이의 double 값으로 리스트의 요소 위치값을 산출하려 한다.
int idx = d * c.size(); // int로 암시적 변환이 발생.
if (idx == c.size()) --idx; // d == 1.0일때도 유효하도록 처리
// 하지만, 일부러 int로 변환했다는 의도가 명확하지 않다.

auto idx = static_cast<int>(d * c.size()); // 타입 명시 초기치 관용구
if (idx == c.size()) --idx; // d == 1.0일때도 유효하도록 처리
728x90
Comments