[Modern C++] 항목 10: 범위 없는 enum보다 범위 있는 enum을 선호하라
카테고리: Cpp
이 글은 아래의 책을 정리하였습니다. 이펙티브 모던 C++, 스콧 마이어스 저자, 류광 번역
가독성이 떨어지는 직역들을 수정하며 정리하였습니다. e.g. 연역 → 추론, 중복적재 → 오버로딩
📦 3. 현대적 C++에 적응하기
👉🏻 항목 10: 범위 없는 enum보다 범위 있는 enum을 선호하라
🔍 열거자 범위
// 범위 없는 enum
enum Color { black, white, red };
auto white = false; // ⚠️ 에러!
enum에 있는 열거자들이 범위 밖으로 새어나간다.범위 없는 enum이라고 부르며, C++98 스타일이다.
// 범위 있는 enum
enum class Color { black, white, red };
auto white = false;
Color c = white; // ⚠️ 에러!
Color c = Color::white; // ✅
enum에 있는 열거자들이 현재 중괄호 범위 내로 한정된다.범위 있는 enum혹은enum 클래스라고 부르며, C++11에서 추가되었다.범위 없는 enum도 여전히 C++11에 존재한다.namespace오염을 줄여준다.
🔄 암묵적 변환
// 범위 없는 enum
enum Color { black, white, red };
vector<size_t> primeFactors(size_t x);
Color c = red;
...
if(c < 14.5) { // ⚠️ Color를 double과 비교
auto factors = primeFactors(c); // ⚠️ Color의 소인수 계산
...
}
- 범위 없는 enum의 열거자들은 암묵적으로:
열거자 타입→정수 타입으로 변환정수 타입→부동소수점 타입으로 변환
- 이로 인해, 의도치 않은 동작이 발생할 수 있다.
// 범위 있는 enum
enum class Color { black, white, red };
vector<size_t> primeFactors(size_t x);
Color c = Color::red;
...
if(c < 14.5) { // ⚠️ 에러! Color와 double 비교!
auto factors = primeFactors(c); // ⚠️ 에러! Color를 size_t로 암묵적 변환 불가!
...
}
if(static_cast<double>(c) < 14.5) {
auto factors = primeFactors(static_cast<size_t>(c));
- 범위 있는 enum은 암묵적 변환이 되지 않는다.
- 변환하고 싶다면, 캐스팅을 사용한다.
📋 전방 선언
// 범위 없는 enum
enum Color; // ⚠️ 에러! 전방 선언 실패!
// 범위 있는 enum
enum class Color; // ✅ 전방 선언
범위 없는 enum은 추가 작업 없이, 전방 선언이 불가능하다.enum이 사용되기 전, 컴파일러가 enum의 바탕 형식을 알고 있어야 하기 때문이다.- 바탕 형식(underlying type): 열거형 요소들이 메모리에 저장될 때 실제 크기와 타입을 결정하는 기반 정수형
// C++98
enum Color { black, white, red }; // 바탕 형식: char
enum Status {
good = 0,
failed = 1,
incomplete = 100,
corrupt = 200,
indeterminate = 0xFFFFFFFF
}; // 바탕 형식: int 이상
범위 없는 enum에서 enum 정의는 괜찮다.- 컴파일러가 enum의 바탕 형식을 선택할 수 있기 때문이다.
- 컴파일러는 메모리 효율을 위해 허용되는 가장 작은 바탕 형식을 선택한다.
- 속도 최적화 시, 보다 큰 바탕 형식을 선택할 수도 있다.
전방 선언을 못하는 경우 생기는 단점:
// 범위 없는 enum
enum Status {
good = 0,
failed = 1,
incomplete = 100,
corrupt = 200,
audited = 500, // 추가된 열거자
indeterminate = 0xFFFFFFFF
};
void continueProcessing(Status s);
// 범위 있는 enum
enum class Status; // 전방 선언
void continueProcessing(Status s);
- 전방 선언을 못하면: 컴파일 의존 관계가 늘어나고, 열거형이 변경되면 시스템 전체를 다시 컴파일해야 하는 상황이 발생한다.
- 전방 선언을 하면:
Status 열거형의 정의가 바뀌어도, 해당 선언을 담은 헤더는 다시 컴파일할 필요가 없다.
C++11 바탕 형식 지원:
// 범위 있는 enum의 기본 바탕 형식은 int
enum class Status;
// 바탕 형식 지정 가능
enum class Status : uint32_t;
// 범위 있는 enum을 정의할 때도 바탕 형식 지정 가능
enum class Status : uint32_t {
good = 0,
failed = 1,
incomplete = 100,
corrupt = 200,
audited = 500,
indeterminate = 0xFFFFFFFF
};
// 범위 없는 enum에 바탕 형식 지정 시 전방 선언 가능
enum Color : uint8_t;
- 범위 있는 enum이 전방 선언이 가능한 이유는, 바탕 형식이 지정되어 있기 때문이다.
- 범위 없는 enum도 바탕 형식을 지정해주면, 전방 선언이 가능해진다.
✅ 범위 없는 enum이 유용한 경우
using UserInfo = tuple<string, string, size_t>;
UserInfo uInfo;
auto val = std::get<1>(uInfo); // 1번 필드가 무엇인지 바로 알 수 없음
// 범위 없는 enum 사용
enum UserInfoFields { uiName, uiEmail, uiReputation };
UserInfo uInfo;
// uiEmail → size_t로의 암묵적 변환 발생
auto val = std::get<uiEmail>(uInfo);
- 범위 없는 enum을 사용하면, 해당 필드가 무엇을 뜻하는지 바로 알 수 있다.
// 범위 있는 enum 사용
enum class UserInfoFields { uiName, uiEmail, uiReputation };
UserInfo uInfo;
auto val =
std::get
static_cast<size_t>(UserInfoFields::uiEmail)
>(uInfo);
- 범위 있는 enum에서 비슷하게 하려면 복잡해진다.
static_cast<size_t>(UserInfoFields::uiEmail)를 줄이고 싶다면,열거자→size_t를 돌려주는 함수toUType을 작성해야 한다.
toUType 함수 조건:
std::get<..>(..)은 템플릿 함수이기에, 컴파일 도중 산출해야 한다. →constexpr함수여야 한다.- 반환 값으로 enum의 바탕 형식을 반환해야 한다. →
std::underlying_type타입 특성 사용 - 예외를 던지지 않으므로,
noexcept로 선언한다.
// C++11
template<typename E>
constexpr typename std::underlying_type<E>::type
toUType(E enumerator) noexcept {
return static_cast
typename std::underlying_type<E>::type
>(enumerator);
}
// C++14: underlying_type_t 사용 (항목 9 참고)
template<typename E>
constexpr std::underlying_type_t<E>
toUType(E enumerator) noexcept {
return static_cast
std::underlying_type_t<E>
>(enumerator);
}
// C++14: auto 반환 타입 사용 (항목 3 참고)
template<typename E>
constexpr auto
toUType(E enumerator) noexcept {
return static_cast
std::underlying_type_t<E>
>(enumerator);
}
auto val = std::get
toUType(UserInfoFields::uiEmail)
>(uInfo);
🧐 정리
- C++98 스타일의 enum을 범위 없는 enum이라 부른다.
- 범위 있는 enum의 열거자들은 현재 중괄호 범위 내에서만 유효하다.
- 암묵적 변환은 허용되지 않으며, 캐스팅을 통해서만 다른 형식으로 변환된다.
댓글남기기