C++ enum class | "강타입 열거형" 가이드
이 글의 핵심
C++ enum class에 대한 실전 가이드입니다.
enum class란?
enum class (또는 scoped enum)는 C++11에서 도입된 타입 안전한 열거형입니다. 기존 enum의 문제점(암시적 변환, 이름 충돌)을 해결하고, 더 강력한 타입 안전성을 제공합니다.
// 일반 enum (C++03)
enum Color {
RED,
GREEN,
BLUE
};
Color c = RED; // OK
int x = RED; // OK (암시적 변환)
// enum class (C++11)
enum class Status {
SUCCESS,
ERROR,
PENDING
};
Status s = Status::SUCCESS; // OK
// int x = Status::SUCCESS; // 에러: 암시적 변환 불가
int x = static_cast<int>(Status::SUCCESS); // 명시적 변환 필요
왜 필요한가?:
- 타입 안전: 암시적 변환 방지
- 스코프: 이름 충돌 방지
- 명확성:
Color::RED처럼 명시적 사용 - 기본 타입 지정: 메모리 절약 가능
// ❌ 일반 enum: 이름 충돌
enum Color { RED, GREEN, BLUE };
enum Status { RED, GREEN }; // 에러: RED 중복
// ✅ enum class: 스코프로 구분
enum class Color { RED, GREEN, BLUE };
enum class Status { RED, GREEN }; // OK
enum vs enum class 비교:
| 특징 | enum | enum class |
|---|---|---|
| 암시적 변환 | ✅ 가능 | ❌ 불가 |
| 스코프 | ❌ 없음 | ✅ 있음 |
| 타입 안전 | ❌ 약함 | ✅ 강함 |
| 이름 충돌 | ❌ 발생 가능 | ✅ 방지 |
| 사용 | RED | Color::RED |
enum class의 장점:
// 1. 타입 안전
enum class Color { RED, GREEN, BLUE };
// int x = Color::RED; // 에러: 암시적 변환 불가
// 2. 스코프
enum class Color { RED };
enum class Status { RED }; // OK: 다른 스코프
// 3. 명확성
Color c = Color::RED; // 명시적
// 4. 기본 타입 지정
enum class SmallEnum : uint8_t { A, B, C }; // 1바이트
기본 사용법
enum class Color {
RED,
GREEN,
BLUE
};
Color c = Color::RED;
if (c == Color::RED) {
cout << "빨강" << endl;
}
// switch문
switch (c) {
case Color::RED:
cout << "빨강" << endl;
break;
case Color::GREEN:
cout << "초록" << endl;
break;
case Color::BLUE:
cout << "파랑" << endl;
break;
}
명시적 값 지정
enum class HttpStatus {
OK = 200,
CREATED = 201,
BAD_REQUEST = 400,
UNAUTHORIZED = 401,
NOT_FOUND = 404,
SERVER_ERROR = 500
};
HttpStatus status = HttpStatus::OK;
int code = static_cast<int>(status); // 200
기본 타입 지정
// 기본: int
enum class Color : int {
RED,
GREEN,
BLUE
};
// 작은 타입 사용 (메모리 절약)
enum class SmallEnum : uint8_t {
A,
B,
C
};
// 큰 타입 사용
enum class BigEnum : uint64_t {
LARGE_VALUE = 1000000000000
};
실전 예시
예시 1: 상태 머신
enum class State {
IDLE,
RUNNING,
PAUSED,
STOPPED
};
class StateMachine {
private:
State currentState = State::IDLE;
public:
void start() {
if (currentState == State::IDLE) {
currentState = State::RUNNING;
cout << "시작" << endl;
}
}
void pause() {
if (currentState == State::RUNNING) {
currentState = State::PAUSED;
cout << "일시정지" << endl;
}
}
void resume() {
if (currentState == State::PAUSED) {
currentState = State::RUNNING;
cout << "재개" << endl;
}
}
void stop() {
currentState = State::STOPPED;
cout << "정지" << endl;
}
State getState() const {
return currentState;
}
};
int main() {
StateMachine sm;
sm.start();
sm.pause();
sm.resume();
sm.stop();
}
상태 머신 심화: 전이 테이블과 무효 전이
간단한 if 연쇄도 동작하지만, 상태가 늘어나면 전이 함수를 한곳에 모으는 편이 유지보수에 유리합니다. 아래는 (현재 상태, 이벤트) → 다음 상태를 표로 두는 스케치입니다. 실무에서는 std::optional<State>나 bool 반환으로 “이 전이는 허용되지 않음”을 표현합니다.
enum class Event { Start, Pause, Resume, Stop };
class StateMachine2 {
State state_{State::IDLE};
static bool tryTransition(State from, Event ev, State& out) {
switch (from) {
case State::IDLE:
if (ev == Event::Start) { out = State::RUNNING; return true; }
return false;
case State::RUNNING:
if (ev == Event::Pause) { out = State::PAUSED; return true; }
if (ev == Event::Stop) { out = State::STOPPED; return true; }
return false;
case State::PAUSED:
if (ev == Event::Resume) { out = State::RUNNING; return true; }
if (ev == Event::Stop) { out = State::STOPPED; return true; }
return false;
case State::STOPPED:
return false;
}
return false;
}
public:
bool dispatch(Event ev) {
State next{};
if (!tryTransition(state_, ev, next)) return false;
state_ = next;
return true;
}
State state() const { return state_; }
};
이 패턴은 로깅·테스트가 쉽고, 나중에 전이 표를 데이터로 빼거나 그래프 도구로 시각화하기도 좋습니다.
예시 2: 에러 코드
enum class ErrorCode {
SUCCESS = 0,
FILE_NOT_FOUND,
PERMISSION_DENIED,
NETWORK_ERROR,
INVALID_INPUT,
UNKNOWN_ERROR
};
class Result {
private:
ErrorCode code;
string message;
public:
Result(ErrorCode c, const string& msg = "")
: code(c), message(msg) {}
bool isSuccess() const {
return code == ErrorCode::SUCCESS;
}
ErrorCode getCode() const {
return code;
}
string getMessage() const {
switch (code) {
case ErrorCode::SUCCESS:
return "성공";
case ErrorCode::FILE_NOT_FOUND:
return "파일을 찾을 수 없음: " + message;
case ErrorCode::PERMISSION_DENIED:
return "권한 거부: " + message;
case ErrorCode::NETWORK_ERROR:
return "네트워크 오류: " + message;
case ErrorCode::INVALID_INPUT:
return "잘못된 입력: " + message;
default:
return "알 수 없는 오류";
}
}
};
Result readFile(const string& filename) {
ifstream file(filename);
if (!file) {
return Result(ErrorCode::FILE_NOT_FOUND, filename);
}
// 파일 읽기
return Result(ErrorCode::SUCCESS);
}
int main() {
Result result = readFile("test.txt");
if (result.isSuccess()) {
cout << "성공!" << endl;
} else {
cout << "에러: " << result.getMessage() << endl;
}
}
예시 3: 방향
enum class Direction {
NORTH,
EAST,
SOUTH,
WEST
};
class Player {
private:
int x = 0, y = 0;
Direction facing = Direction::NORTH;
public:
void move(Direction dir) {
switch (dir) {
case Direction::NORTH:
y++;
break;
case Direction::EAST:
x++;
break;
case Direction::SOUTH:
y--;
break;
case Direction::WEST:
x--;
break;
}
facing = dir;
}
void turnLeft() {
facing = static_cast<Direction>(
(static_cast<int>(facing) + 3) % 4
);
}
void turnRight() {
facing = static_cast<Direction>(
(static_cast<int>(facing) + 1) % 4
);
}
void print() const {
cout << "위치: (" << x << ", " << y << ")" << endl;
cout << "방향: ";
switch (facing) {
case Direction::NORTH: cout << "북"; break;
case Direction::EAST: cout << "동"; break;
case Direction::SOUTH: cout << "남"; break;
case Direction::WEST: cout << "서"; break;
}
cout << endl;
}
};
int main() {
Player player;
player.move(Direction::NORTH);
player.move(Direction::NORTH);
player.turnRight();
player.move(Direction::EAST);
player.print();
}
예시 4: 로그 레벨
enum class LogLevel {
DEBUG,
INFO,
WARNING,
ERROR,
FATAL
};
class Logger {
private:
LogLevel minLevel = LogLevel::INFO;
public:
void setLevel(LogLevel level) {
minLevel = level;
}
void log(LogLevel level, const string& message) {
if (level >= minLevel) {
cout << "[" << levelToString(level) << "] "
<< message << endl;
}
}
void debug(const string& msg) { log(LogLevel::DEBUG, msg); }
void info(const string& msg) { log(LogLevel::INFO, msg); }
void warning(const string& msg) { log(LogLevel::WARNING, msg); }
void error(const string& msg) { log(LogLevel::ERROR, msg); }
void fatal(const string& msg) { log(LogLevel::FATAL, msg); }
private:
string levelToString(LogLevel level) const {
switch (level) {
case LogLevel::DEBUG: return "DEBUG";
case LogLevel::INFO: return "INFO";
case LogLevel::WARNING: return "WARNING";
case LogLevel::ERROR: return "ERROR";
case LogLevel::FATAL: return "FATAL";
default: return "UNKNOWN";
}
}
};
int main() {
Logger logger;
logger.debug("디버그 메시지"); // 출력 안됨
logger.info("정보 메시지");
logger.warning("경고 메시지");
logger.error("에러 메시지");
logger.setLevel(LogLevel::DEBUG);
logger.debug("이제 출력됨");
}
enum class 변환
enum class Color {
RED,
GREEN,
BLUE
};
// enum class -> int
Color c = Color::RED;
int x = static_cast<int>(c);
// int -> enum class
int y = 1;
Color c2 = static_cast<Color>(y);
// 문자열 변환 (헬퍼 함수)
string toString(Color c) {
switch (c) {
case Color::RED: return "RED";
case Color::GREEN: return "GREEN";
case Color::BLUE: return "BLUE";
default: return "UNKNOWN";
}
}
자주 발생하는 문제
문제 1: 암시적 변환
enum class Color {
RED,
GREEN,
BLUE
};
// ❌ 암시적 변환 불가
// int x = Color::RED;
// ✅ 명시적 변환
int x = static_cast<int>(Color::RED);
문제 2: 비교 연산
enum class Color {
RED,
GREEN,
BLUE
};
// ❌ 다른 enum class와 비교 불가
enum class Status {
OK,
ERROR
};
// Color c = Color::RED;
// if (c == Status::OK) {} // 에러
// ✅ 같은 enum class끼리만 비교
Color c = Color::RED;
if (c == Color::RED) {} // OK
문제 3: 스코프
// ❌ 스코프 없이 사용 불가
enum class Color {
RED,
GREEN,
BLUE
};
// Color c = RED; // 에러
// ✅ 스코프 필수
Color c = Color::RED;
실무 패턴
패턴 1: 비트 플래그
enum class는 기본적으로 비트 연산을 지원하지 않으므로, 플래그 전용 타입이라면 | & ~ ^를 자유롭게 쓰려면 아래처럼 연산자 오버로딩과 constexpr 헬퍼를 함께 두는 경우가 많습니다. std::underlying_type_t<E>로 캐스팅하면 의도가 드러납니다.
template<class E>
constexpr auto to_underlying(E e) noexcept {
return static_cast<std::underlying_type_t<E>>(e);
}
비트 플래그 예시 (기존 패턴 보강)
enum class Permission : uint32_t {
NONE = 0,
READ = 1 << 0, // 1
WRITE = 1 << 1, // 2
EXECUTE = 1 << 2 // 4
};
// 비트 연산자 오버로딩
Permission operator|(Permission lhs, Permission rhs) {
return static_cast<Permission>(
static_cast<uint32_t>(lhs) | static_cast<uint32_t>(rhs)
);
}
Permission operator&(Permission lhs, Permission rhs) {
return static_cast<Permission>(
static_cast<uint32_t>(lhs) & static_cast<uint32_t>(rhs)
);
}
bool hasPermission(Permission flags, Permission check) {
return (flags & check) == check;
}
// 사용
Permission userPerms = Permission::READ | Permission::WRITE;
if (hasPermission(userPerms, Permission::READ)) {
std::cout << "읽기 권한 있음\n";
}
enum을 쓰면 암시적으로 정수로 승격되어 실수로 +만 해도 플래그가 꼬일 수 있습니다. enum class는 정수와 섞이지 않게 막아 주므로, 권한·OpenGL/Vulkan 스타일 비트마스크·파일 open 플래그 등에 적합합니다. 단위 테스트에서는 모든 비트 조합을 다 돌리기보다 대표적인 조합과 경계(0, all bits) 위주로 검증하면 됩니다.
패턴 2: 문자열 변환 (매크로)
#define ENUM_TO_STRING(EnumType) \
inline const char* toString(EnumType value) { \
switch (value) {
#define ENUM_CASE(EnumValue) \
case EnumValue: return #EnumValue;
#define END_ENUM_TO_STRING \
default: return "UNKNOWN"; \
} \
}
enum class Color { RED, GREEN, BLUE };
ENUM_TO_STRING(Color)
ENUM_CASE(Color::RED)
ENUM_CASE(Color::GREEN)
ENUM_CASE(Color::BLUE)
END_ENUM_TO_STRING
// 사용
std::cout << toString(Color::RED) << '\n'; // "Color::RED"
패턴 3: 범위 기반 순회
enum class Color { RED, GREEN, BLUE, COUNT };
template<typename Enum>
class EnumIterator {
using value_type = Enum;
value_type value_;
public:
EnumIterator(value_type value) : value_(value) {}
EnumIterator& operator++() {
value_ = static_cast<Enum>(static_cast<int>(value_) + 1);
return *this;
}
bool operator!=(const EnumIterator& other) const {
return value_ != other.value_;
}
Enum operator*() const {
return value_;
}
};
template<typename Enum>
class EnumRange {
Enum begin_;
Enum end_;
public:
EnumRange(Enum begin, Enum end) : begin_(begin), end_(end) {}
EnumIterator<Enum> begin() const {
return EnumIterator<Enum>(begin_);
}
EnumIterator<Enum> end() const {
return EnumIterator<Enum>(end_);
}
};
// 사용
for (Color c : EnumRange(Color::RED, Color::COUNT)) {
std::cout << static_cast<int>(c) << '\n';
}
FAQ
Q1: enum vs enum class?
A:
- enum: 암시적 변환 가능, 스코프 없음, 이름 충돌 가능
- enum class: 타입 안전, 스코프 있음, 명시적 변환 필요
// enum: 암시적 변환
enum Color { RED };
int x = RED; // OK
// enum class: 명시적 변환
enum class Status { RED };
// int x = Status::RED; // 에러
int x = static_cast<int>(Status::RED); // OK
권장: 새 코드에서는 enum class 사용
Q2: enum class는 언제 사용해야 하나요?
A:
- 타입 안전성이 필요할 때: 암시적 변환 방지
- 이름 충돌을 방지하고 싶을 때: 스코프 제공
- 명확한 코드를 원할 때:
Color::RED처럼 명시적
// 타입 안전
enum class Color { RED };
// if (Color::RED == 0) {} // 에러
// 이름 충돌 방지
enum class Color { RED };
enum class Status { RED }; // OK
// 명확성
Color c = Color::RED; // 명시적
Q3: 기본 타입 지정은 어떻게 하나요?
A: : type 문법을 사용합니다. 메모리 절약이나 큰 값 저장에 유용합니다.
// 작은 타입 (메모리 절약)
enum class SmallEnum : uint8_t {
A, B, C
}; // 1바이트
// 큰 타입 (큰 값 저장)
enum class BigEnum : uint64_t {
LARGE_VALUE = 1000000000000
}; // 8바이트
// 기본: int (4바이트)
enum class DefaultEnum {
A, B, C
};
Q4: enum class를 문자열로 변환하려면?
A: switch문이나 map으로 직접 구현해야 합니다. C++에는 내장 기능이 없습니다.
enum class Color { RED, GREEN, BLUE };
// switch문 사용
std::string toString(Color c) {
switch (c) {
case Color::RED: return "RED";
case Color::GREEN: return "GREEN";
case Color::BLUE: return "BLUE";
default: return "UNKNOWN";
}
}
// map 사용
std::map<Color, std::string> colorNames = {
{Color::RED, "RED"},
{Color::GREEN, "GREEN"},
{Color::BLUE, "BLUE"}
};
Q5: enum class로 비트 플래그를 사용할 수 있나요?
A: 가능하지만 비트 연산자를 오버로딩해야 합니다.
enum class Flags : uint32_t {
NONE = 0,
FLAG_A = 1 << 0,
FLAG_B = 1 << 1,
FLAG_C = 1 << 2
};
Flags operator|(Flags lhs, Flags rhs) {
return static_cast<Flags>(
static_cast<uint32_t>(lhs) | static_cast<uint32_t>(rhs)
);
}
// 사용
Flags flags = Flags::FLAG_A | Flags::FLAG_B;
Q6: enum class의 값을 순회하려면?
A: 범위 기반 for를 직접 구현하거나, 값을 배열에 저장하여 순회합니다.
enum class Color { RED, GREEN, BLUE, COUNT };
// 방법 1: 배열 사용
std::array<Color, 3> colors = {
Color::RED, Color::GREEN, Color::BLUE
};
for (Color c : colors) {
// ...
}
// 방법 2: 범위 기반 순회 구현 (위 "실무 패턴 3" 참조)
Q7: enum class 학습 리소스는?
A:
- “Effective Modern C++” (Item 10: Prefer scoped enums to unscoped enums) by Scott Meyers
- cppreference.com - Enumeration declaration
- “C++ Primer” (5th Edition) by Stanley Lippman
관련 글: Enum Basics, Bit Manipulation.
한 줄 요약: enum class는 타입 안전하고 스코프가 있는 C++11 열거형입니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ 범위 기반 for | “Range-based for” 가이드
- C++ Atomic Operations | “원자적 연산” 가이드
- C++ explicit Keyword | “explicit 키워드” 가이드
관련 글
- C++ async & launch |
- C++ Atomic Operations |
- C++ Attributes |
- C++ auto 키워드 |
- C++ auto 타입 추론 | 복잡한 타입을 컴파일러에 맡기기