[Modern C++] 항목 6: auto가 원치 않는 형식으로 연역될 때에는 명시적 형식의 초기치를 사용하라
카테고리: Cpp
이 글은 아래의 책을 정리하였습니다. 이펙티브 모던 C++, 스콧 마이어스 저자, 류광 번역
📦 2. auto
👉🏻 항목 6: auto가 원치 않는 형식으로 연역될 때에는 명시적 형식의 초기치를 사용하라
⚠️ 문제 상황
// 반환 값의 각 bool은 특정 기능 지원 여부를 뜻한다.
vector<bool> features(const Widget& w);
Widget w;
bool highPriority = features(w)[5];
processWidget(w, highPriority);
bool로 명시하면 정상 동작한다.
// 반환 값의 각 bool은 특정 기능 지원 여부를 뜻한다.
vector<bool> features(const Widget& w);
Widget w;
auto highPriority = features(w)[5];
// ⚠️ 컴파일은 되지만, 미정의 동작!
processWidget(w, highPriority);
auto로 바꾸면 컴파일은 된다.- 하지만, 원치 않는 형식으로 연역되어 미정의 동작이 수행된다.
bool로 연역되지 않는다!
🔎 문제 이유 1: vector<bool>의 특이성
vector<bool>은 메모리 효율을 위해 각bool을 1비트로 압축 저장- C++은 비트에 대한 참조가 금지되어,
vector<T>::operator[]는T&을 돌려줄 수 없음 - 우회책으로
bool&처럼 동작하는 프록시 객체vector<bool>::reference를 반환
🔎 문제 이유 2: 댕글링 포인터
auto highPriority = features(w)[5];
features(w)호출 →vector<bool>임시 객체 생성operator[](5)호출 →vector<bool>::reference반환vector<bool>::reference는 임시 객체 내부 비트 포인터 + offset(5) 를 보유
auto→vector<bool>::reference형식 연역- 문장 끝에서 임시 객체 파괴 →
reference가 가진 포인터가 댕글링 포인터가 됨
bool로 명시하면 왜 안전했는가?
vector<bool>::reference→bool암묵적 변환이 임시 객체가 살아있는 동안 발생하기 때문
🧩 프록시 클래스
- 표현식 템플릿 기법을 사용하는 C++ 라이브러리에서도 흔히 쓰인다.
Matrix sum = m1 + m2 + m3 + m4;
- 두
Matrix객체에 대한operator+가Matrix를 반환하는 것은 비효율적이다. - 대신,
Sum<Matrix, Matrix>와 같은 프록시 클래스 객체를 반환한다.
Matrix sum = Sum<Sum<Sum<Matrix, Matrix>, Matrix>, Matrix>;
- 프록시 객체가 연쇄된 형태로 변환된다.
- 사용자가 프록시 객체의 존재를 모르게 하는 것이 좋다.
- 즉, 프록시 객체를 직접 쓰지 않도록 해야 한다.
- 프록시 클래스는
Matrix로의 암묵적 변환을 지원하기에, 효율적으로 처리할 수 있다.
namespace std {
template <class Allocator>
class vector<bool, Allocator> {
public:
class reference { ... };
reference operator[](size_type n);
};
}
vector<bool>::operator[]의 명세이다.operator[]는T&이 아니라reference(프록시 객체) 를 반환한다.- 이렇듯, 직접 보기 전까지 프록시 객체의 존재를 알지 못해야 한다.
✅ 해결 방법: static_cast
auto highPriority = static_cast<bool>(features(w)[5]);
// Matrix도 이제 auto를 사용할 수 있음
auto Matrix = static_cast<Matrix>(m1 + m2 + m3 + m4);
- 형식 명시 초기치 관용구인
static_cast를 사용하면 된다. features(w)[5]→reference→bool(캐스팅)이 된다.auto도 이제 정상적으로bool로 연역된다.
추가 사용법:
// double을 반환하는 함수
double calcEpsilon();
// 명시적 (double -> float가 의도적인 것인가?)
float ep = calcEpsilon();
// auto + static_cast (float 변환이 의도된 것임을 알 수 있음)
auto ep = static_cast<float>(calcEpsilon());
// 명시적 (double -> int가 의도된 것인가?)
int idx = d * c.size(); // d * c.size() = double형
if(idx == c.size()) --idx;
// auto + static_cast
auto idx = static_cast<int>(d * c.size());
if(idx == c.size()) --idx;
- 변환이 의도된 것임을 알리는 데 사용할 수도 있다.
🧐 정리
- “보이지 않는” 프록시 형식 때문에
auto가 초기화 표현식의 형식을 잘못 연역할 수 있다. - 형식 명시 초기치 관용구(
static_cast) 는auto가 원하는 형식을 연역하도록 강제한다.
댓글남기기