[Effective Modern C++] 6. auto가 원치 않은 타입으로 추론 될 때에는 명시적 타입의 초기화를 생각하자
728x90
반응형
일단, 보이지 않는 프록시 타입 때문에 auto가 초기화 표현식의 타입을 잘못 추론할 수 있다는 점을 알아야한다.
예를 들어, std::vector<bool>::operator[]의 반환 타입은 bool&이 아니라 어떤 프록시 클래스 타입이다.
프록시 클래스 중에는 사용자에게 명백히 드러나도록 설계된 것들도 있지만(std::shared_ptr, std::unique_ptr) 보통 보이지 않도록 숨겨져 있다.
그렇기 때문에 보통 프록시 클래스는 auto와 잘 맞지 않는다. 때문에 다음과 같은 코드는 미정의 동작을 유발한다.
// Widget을 하나 받고 std::vector<bool>을 돌려주는 다음과 같은 함수가 하나 있다고 하자. std::vector<bool> features(const Widget& w); // 해당 bool 값들은 그 Widget이 특정 기능(feature)을 지원하는지를 뜻한다고 가정한다. // 더 나아가서, 5번 비트는 주어진 Widget의 우선순위가 높은지를 나타낸다고 가정하자. // 이 함수를 이용해서 다음과 같은 코드를 작성할 수 있을 것이다. Widget w; ... bool highPriority = features(w)[5]; // w의 우선순위가 높은가? ... processWidget(w, highPriority); // w를 그 우선순위에 맞게 처리한다. // 이 코드는 딱히 문제될 것이 없다. 하지만 bool 대신 auto로 highPriority를 선언하면? auto highPriority = features(w)[5]; // w의 우선순위가 높은가? // 여전히 컴파일되지만, 이제는 그 행동을 예측할 수 없다. processWidget(w, highPriority); // 미정의 행동! // 그 이유는 std::vector<bool>::operator[]가 bool&을 반환하는 것이 아니라, std::vector<bool>::reference라는 프록시 객체를 반환하고, 이러한 객체의 수명이 한 문장 이상으로 연장되지 않도록 만들어지는 것이 보통이기 때문이다. // 즉, processWidget이 호출될 시점에는 이미 highPriority 객체(std::vector<bool>::reference 객체) 내부의 포인터는 이미 소멸된 대상을 가리키고 있게 된다. // 정리하자면, 다음과 같은 형태의 코드는 피해야 한다. auto someVar = "보이지 않는" 프록시 클래스 타입의 표현식;
보이지 않는 프록시 클래스는 일상적인 용법에서는 그 존재가 드러나지 않도록 설계된 것이지만, 그래도 라이브러리의 문서에는 프록시 클래스의 존재가 명시되어 있는 경우가 많다.
또한 문서화의 결함을 헤더 파일이 채워주기도 한다. 소스 코드에서 프록시 객체의 존재를 완전이 숨길 수 있는 경우는 거의 없다.
일반적으로 프록시 객체는 사용자가 호출하도록 만들어진 어떤 함수가 돌려주며, 그런 함수의 시그니처를 보면 프록시 객체의 존재를 확인할 수 있는 경우가 많다.
// std::vector<bool>::operator[]의 명세이다. namespace std { template <class Allocator> class vector<bool, Allocator> { public: ... class reference { ... }; reference operator[](size_type n); ... }; } // 보통의 경우 std::vector<T>의 operator[]가 T&를 돌려준다는 사실을 알고 있다면 이 operator[]의 반환 타입을 생소하게 느낄 것이며, 그러한 생소함은 곧 이 부분에서 프록시 클래스가 쓰이고 있음을 암시해주는 힌트라 할 수 있다.
타입 명시 초기치 관용구는 초기화에 쓰이는 표현식이 산출하는 타입과는 다른 타입으로 변수를 생성하고자 하는 의도를 명확하게 나타내는 데도 유용하다.
아까 문제 되었던 코드를 다시 보자.
std::vector<bool> features(const Widget& w); Widget w; ... auto highPriority = features(w)[5]; // features의 반환 타입은 std::vector<bool>이고 std::vector<bool>::operator[]의 반환 타입은 std::vector<bool>::reference인데, 우리가 필요한 것은 bool 타입이다. // 다음은 highPriority가 bool로 추론되도록 강제하는 예이다. auto highPriority = static_cast<bool>(features(w)[5]); --- // 타입 명시 초기치 관용구는 초기화에 쓰이는 표현식이 산출하는 타입과는 다른 타입으로 변수를 생성 하고자하는 의도를 명확하게 나타내는 데에도 유용하다. // 예를 들어 어떤 허용 한계치를 계산하는 다음과 같은 함수가 있다고 하자. double calcEpsilon(void); // 허용 한계치를 돌려준다. // 그런데 float의 정밀도로 충분하며, double이 float에 비해 크다는 점이 걱정이라고 하자. // 그럼 calcEpsilon의 결과를 float에 담으면 된다. float ep = calcEpsilon(); // double에서 float로의 암묵적 변환이 일어남 // 그런데 이 코드는 "함수가 돌려준 값의 정밀도를 일부러 줄이고자 한다"는 의도를 명확하게 표현하지 않는다. // 타입 명시 초기치 관용구를 이용하면 그런 의도가 표현된다. auto ep = static_cast<float>(calcEpsilon());
위처럼 타입 명시 초기치 관용구를 이용하면 의도를 명확하게 나타낼 수 있다.
요약
- 보이지 않는 프록시 타입 때문에 auto가 초기화 표현식의 타입을 잘못 추론할 수 있다.
- 타입 명시 초기치 관용구는 auto가 원하는 타입을 추론하도록 강제한다.
728x90
반응형
'Books > Effective Modern C++' 카테고리의 다른 글
[Effective Modern C++] 8. 0과 NULL보다 nullptr을 사용하자 (0) | 2022.08.28 |
---|---|
[Effective Modern C++] 7. 객체 생성 시 괄호(())와 중괄호({})를 구분하자 (0) | 2022.08.28 |
[Effective Modern C++] 5. 명시적 타입 선언보다는 auto를 선호하자 (0) | 2022.08.15 |
[Effective Modern C++] 4. 추론된 타입을 파악하는 방법을 알아두자 (0) | 2022.08.06 |
[Effective Modern C++] 3. decltype의 작동 방식을 숙지하자 (0) | 2022.08.06 |
댓글
이 글 공유하기
다른 글
-
[Effective Modern C++] 8. 0과 NULL보다 nullptr을 사용하자
[Effective Modern C++] 8. 0과 NULL보다 nullptr을 사용하자
2022.08.28리터럴 0은 int이지 포인터가 아니다. 실용적인 관점에서는 NULL도 마찬가지이다. 그러나 NULL의 경우에는 다소 불확실한 세부사항이 존재한다. 구현이 NULL에 int 이외의 정수 타입을 부여할 수도 있기 때문이다. 이 점이 이야기하는 주된 문제는 포인터와 정수 타입에 대한 오버로딩이 의외의 방식으로 해소된다는 점이었다. 0이나 NULL로 그런 오버로딩 된 함수를 호출했을 때, 포인터를 받는 오버로딩된 함수가 호출되는 일은 없다. 반면 nullptr을 사용하면 오버로딩이 예상과 다르게 해소되는 일이 없다. 그 뿐만 아니라, nullptr는 코드의 명확성도 높여준다. 템플릿의 타입 추론시에도, 0과 NULL은 정수 타입으로 추론하는 반면 nullptr은 포인터 타입으롤 추론한다. // f의 세 가지 … -
[Effective Modern C++] 7. 객체 생성 시 괄호(())와 중괄호({})를 구분하자
[Effective Modern C++] 7. 객체 생성 시 괄호(())와 중괄호({})를 구분하자
2022.08.28 -
[Effective Modern C++] 5. 명시적 타입 선언보다는 auto를 선호하자
[Effective Modern C++] 5. 명시적 타입 선언보다는 auto를 선호하자
2022.08.15 -
[Effective Modern C++] 4. 추론된 타입을 파악하는 방법을 알아두자
[Effective Modern C++] 4. 추론된 타입을 파악하는 방법을 알아두자
2022.08.06
댓글을 사용할 수 없습니다.