Abseil Tip 86 클래스(enum class)를 활용한 열거형


title: “이번 주의 팁 #86: 클래스(enum class)를 활용한 열거형” layout: tips sidenav: side-nav-tips.html published: true permalink: tips/86 type: markdown order: “086” —

원래 totw/86로 2015-01-05에 게시됨

작성자: Bradley White (bww@google.com)

“품격을 보여주고, … 그리고 캐릭터를 드러내라.” - Bear Bryant

열거형(enumeration) 또는 줄여서 enum은 특정 정수 집합 중 하나의 값을 가질 수 있는 타입입니다. 이 집합의 일부 값에 이름을 부여할 수 있으며, 이를 열거자(enumerator)라고 부릅니다.

비스코프 열거형 (Unscoped Enumerations)

이 개념은 C++ 프로그래머에게 익숙할 것입니다. 그러나 C++11 이전의 열거형에는 두 가지 큰 단점이 있었습니다:

  1. 열거자 이름이 열거형 타입과 동일한 스코프에 존재했습니다.
  2. 열거자는 암묵적으로 정수형 타입으로 변환되었습니다.

C++98에서의 예시:

enum CursorDirection { kLeft, kRight, kUp, kDown };
CursorDirection d = kLeft; // 가능: 열거자가 같은 스코프에 있음
int i = kRight;            // 가능: 열거자가 정수형으로 변환됨

그러나 다음과 같은 경우에는 문제가 발생합니다:

// 오류: kLeft와 kRight가 중복 선언됨
enum PoliticalOrientation { kLeft, kCenter, kRight };

C++11에서의 변경 사항

C++11에서는 비스코프 열거형의 동작을 약간 변경했습니다. 열거자는 이제 열거형 내부에 로컬로 존재하지만, 이전 버전과의 호환성을 위해 여전히 해당 열거형의 스코프로 내보내집니다.

CursorDirection d = CursorDirection::kLeft;  // C++11에서 가능
int i = CursorDirection::kRight;             // 가능: 여전히 정수형으로 변환됨

하지만 PoliticalOrientation의 선언은 여전히 오류를 발생시킵니다.

스코프 열거형 (Scoped Enumerations)

암묵적인 정수형 변환은 흔히 발생하는 버그의 원인이 되며, 열거자가 같은 스코프에 존재함으로 인해 네임스페이스 오염이 발생해 대규모 프로젝트에서는 문제가 될 수 있습니다. 이러한 문제를 해결하기 위해, C++11은 새로운 개념인 스코프 열거형(scoped enum)을 도입했습니다.

스코프 열거형에서는 enum class 키워드를 사용하며, 이 경우 열거자는:

  1. 해당 열거형에만 로컬로 존재하며 (열거형의 스코프로 내보내지 않음),
  2. 정수형 타입으로 암묵적으로 변환되지 않습니다.

예시 (추가된 class 키워드 주목):

enum class CursorDirection { kLeft, kRight, kUp, kDown };
CursorDirection d = kLeft;                    // 오류: kLeft가 이 스코프에 없음
CursorDirection d2 = CursorDirection::kLeft;  // 가능
int i = CursorDirection::kRight;              // 오류: 변환 불가

그리고 다음과 같이:

// 가능: kLeft와 kRight는 각각의 스코프 열거형에 로컬로 존재함
enum class PoliticalOrientation { kLeft, kCenter, kRight };

이러한 간단한 변경으로 기존의 열거형 문제를 해결할 수 있으며, 새로운 코드에서는 enum class를 사용하는 것이 권장됩니다.

스코프 열거형을 사용하면 여전히 정수형 타입으로 변환이 필요할 때는 명시적으로 캐스팅해야 합니다 (예: 열거형 값을 로그에 출력하거나 플래그와 같은 열거자에 비트 연산을 사용하는 경우). 그러나 std::hash를 사용하는 해싱 작업(std::unordered_map<CursorDirection, int> 등)에는 여전히 문제가 없습니다.

열거형의 기본 타입 지정

C++11은 또한 두 종류의 열거형 모두에 대해 기본 타입을 지정할 수 있는 기능을 추가했습니다. 이전에는 열거형의 기본 정수형 타입이 열거자들의 부호와 크기를 보고 결정되었으나, 이제는 명시적으로 지정할 수 있습니다.

예시:

// CursorDirection의 기본 타입을 "int"로 사용
enum class CursorDirection : int { kLeft, kRight, kUp, kDown };

열거자 범위가 작고, CursorDirection 값을 저장할 때 메모리를 절약하고 싶다면 char로 지정할 수도 있습니다.

// CursorDirection의 기본 타입을 "char"로 사용
enum class CursorDirection : char { kLeft, kRight, kUp, kDown };

컴파일러는 열거자 값이 기본 타입의 범위를 초과하면 오류를 발생시킵니다.

결론

새로운 코드에서는 enum class를 사용하는 것이 좋습니다. 이렇게 하면 네임스페이스 오염을 줄일 수 있고, 암묵적인 변환에서 발생할 수 있는 버그를 피할 수 있습니다.

enum class Parting { kSoLong, kFarewell, kAufWiedersehen, kAdieu };

reference

https://github.com/abseil/abseil.github.io/blob/master/_posts/2017-10-26-totw-86.md