Menu

C++ 열거형: enum class와 일반 enum의 차이 설명

C++ 열거형을 배워보세요. 선언하는 방법, 스코프가 있는 enum class가 일반 enum보다 안전한 이유, 사용자 지정 기반 값, 열거자에 대한 switch, 그리고 정수와의 상호 변환을 다룹니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

enum은 무엇을 위한 것인가

구조체는 관련된 여러 값을 하나의 객체로 묶습니다. enum은 다른 문제를 해결합니다. 작고 고정된 선택지 집합에 이름을 붙이는 것입니다. 0이 "빨강", 1이 "초록", 2가 "파랑"을 뜻한다고 외우는 대신 Color::Red라고 쓰면, 컴파일러가 당신이 실수하지 않도록 지켜줍니다.

변수가 이름 붙은 몇 안 되는 상태 중 하나만 가질 수 있을 때 —신호등 색, 카드의 무늬, 연결 상태 등— 언제나 enum에 손을 뻗으면 코드가 스스로를 설명하게 되고, 벌거벗은 정수로는 결코 잡아내지 못할 오타와 빠진 경우를 컴파일러가 잡아낼 수 있게 됩니다.

enum 선언하기

모던 C++에는 두 가지 종류가 있습니다. 거의 항상 손을 뻗어야 하는 쪽부터 시작하세요. 스코프가 있는 enum class입니다. 이름을 나열하면, 각 열거자는 열거형의 이름을 통해 ::로 접근합니다:

Color::Green이라고 쓰지, 결코 벌거벗은 Green이라고 쓰지 않는다는 점에 주목하세요. 값 자체는 그저 라벨일 뿐입니다. 비교하고, 대입하고, 여기저기 전달하지만, 그 밑에 깔린 숫자에는 거의 신경 쓰지 않습니다. 기본적으로 Red0, Green1, Blue2로, 0부터 위로 세어 올라갑니다.

일반 enum vs enum class

더 오래된, 스코프 없는 enum(class 키워드 없음)은 이름을 주변 스코프에 곧바로 쏟아내고 스스로 int로 변환됩니다. 편리하게 들리지만, 두 가지 실질적인 문제를 일으킵니다:

enum Color { Red, Green, Blue };
enum Fruit { Apple, Banana, Red };  // error: 'Red' already declared

enum Status { Active, Inactive };
int x = Active;          // compiles silently - is this what you meant?
if (Active == Banana) {  // compares unrelated enums via int - allowed!
}

일반 열거자는 전역 이름이기 때문에, 두 열거형이 라벨 하나를 공유하는 것만으로도 충돌할 수 있습니다. 게다가 int로 격하되기 때문에, 컴파일러는 전혀 관련 없는 열거형의 값들을 태연히 비교합니다. 스코프가 있는 enum class는 둘 다 고칩니다. 이름은 타입 안에 살고, 타입은 조용히 int로 둔갑하지 않습니다:

경험칙: 기본적으로 enum class를 사용하세요. 오래된 C 스타일 플래그 상수처럼 암묵적인 int 변환이 특별히 필요할 때만 일반 enum으로 돌아가세요.

사용자 지정 값과 기반 타입

열거자에 명시적인 숫자를 할당할 수 있습니다. 비워 둔 것들은 이전 값에서부터 계속 위로 세어 올라가므로, HTTP 상태 코드나 비트 플래그 같은 것에 편리합니다:

모든 enum은 정수 타입으로 뒷받침됩니다 —기본적으로 int입니다. 크기가 중요할 때, 예를 들어 압축된 구조체에 많은 enum을 저장하거나 와이어 포맷에 맞출 때는 더 작은 타입으로 고정할 수 있습니다:

기반 타입을 고르는 것은 값이 들어가야 할 범위도 보장합니다. uint8_t enum은 255를 넘는 값을 담을 수 없습니다.

enum과 int 사이 변환하기

스코프가 있는 enum class는 결코 암묵적으로 변환되지 않습니다. 바로 그것이 핵심입니다. 숫자가 정말로 필요할 때 —출력하거나, 배열을 인덱싱하거나, 파일에서 읽을 때— 는 static_cast에 손을 뻗으세요. enum에서 int로 가는 것은 언제나 안전합니다:

int를 enum으로 되돌리는 변환이 위험한 방향입니다. 캐스트는 그 숫자가 실제 열거자에 해당하는지 확인하지 않습니다. 기술적으로 enum의 이름 붙은 집합 바깥에 있는 값을 건네줍니다:

Suit s = static_cast<Suit>(2);   // fine - that's Clubs
Suit bad = static_cast<Suit>(99); // compiles, but 99 is not a valid Suit
// using `bad` in a switch or as an array index is a lurking bug

정수가 사용자 입력이나 파일에서 온다면, 캐스트하기 전에 직접 범위를 검증하세요. 그렇지 않으면 어떤 case도 결코 처리하지 않는 값을 만들어내게 되며, 이는 나중에 정의되지 않은 동작이라는 미묘한 원인이 됩니다.

switch와 함께 enum 사용하기

enum은 "고정된 집합 중 하나"이므로 switch와 완벽하게 어울립니다. 모든 열거자를 다루어 두면, 나중에 새 값을 추가하고 처리하는 것을 잊었을 때 많은 컴파일러가 경고해 줍니다 —날것의 정수로는 얻을 수 없는, 공짜로 얻는 안전성입니다:

한 가지 함정: 열거자의 이름을 출력하는 내장 방법은 없습니다. cout << TrafficLight::Red는 스코프가 있는 enum에서는 컴파일되지 않으며, 일반 enum이라 해도 "Red"가 아니라 숫자를 출력합니다. 위와 같은 작은 switch나 조회 테이블이 enum을 사람이 읽을 수 있는 문자열로 바꾸는 일반적인 방법입니다.

다음: 예외

enum과 구조체는 데이터가 어떻게 생겼는지를 모델링하게 해 줍니다. 하지만 실제 프로그램은 일이 잘못되는 경우도 다루어야 합니다 —열리지 않는 파일, 파싱되지 않는 숫자, 범위를 벗어난 값 등. C++는 이러한 실패 경로를 예외로 처리하며, 그것이 다음 페이지의 주제입니다.

자주 묻는 질문

C++에서 enum과 enum class의 차이는 무엇인가요?

일반 enum은 자신의 이름을 주변 스코프로 흘려보내고 암묵적으로 int로 변환되어, 이름 충돌과 의도치 않은 비교를 일으킵니다. 스코프가 있는 enum class는 이름을 열거형 안에 유지하며(Color::Red), 명시적 캐스트 없이는 int로 변환되기를 거부합니다. 모던 C++에서는 enum class를 선호하세요. 타입 안전하며 고전적인 함정을 피할 수 있습니다.

C++에서 enum을 int로 어떻게 변환하나요?

일반 enum은 암묵적으로 변환되므로 int n = Red;가 그대로 동작합니다. 스코프가 있는 enum class는 명시적 캐스트가 필요합니다: int n = static_cast<int>(Color::Red);. 반대 방향으로 가려면 int를 다시 캐스트하세요: Color c = static_cast<Color>(2);. 다만 주의하세요. 런타임은 그 값이 유효한 열거자인지 확인하지 않습니다.

C++의 첫 번째 enum은 어떤 값에서 시작하나요?

기본적으로 첫 번째 열거자는 0이고, 그 다음 각각은 이전 값보다 1 큽니다. 따라서 enum class Level { Low, Mid, High };에서 Low0, Mid1, High2입니다. 이 동작을 재정의하려면 어느 것에든 명시적인 값을 할당할 수 있습니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기