C++ enum class | Scoped Enumerations Explained
이 글의 핵심
Strongly typed scoped enums in C++11: no implicit int conversion, explicit underlying types, switch hygiene, and bit flags with constexpr helpers.
What is enum class?
Scoped enumeration (enum class) is a strongly typed enum: enumerators live under the enum’s scope and do not implicitly convert to integers.
enum class Status { OK, Error, Pending };
Status s = Status::OK;
// int x = s; // error
int x = static_cast<int>(s);
Compared to plain enum
| Feature | enum | enum class |
|---|---|---|
| Implicit int conversion | Yes | No |
| Scoped enumerators | No | Yes |
| Name clashes | Common | Rare |
Basic usage
enum class Color { Red, Green, Blue };
Color c = Color::Red;
if (c == Color::Red) { /* ....*/ }
switch (c) {
case Color::Red: break;
case Color::Green: break;
case Color::Blue: break;
}
Underlying type
enum class Small : std::uint8_t { A, B, C };
enum class Flags : std::uint32_t { Read = 1 << 0, Write = 1 << 1 };
Bit flags
enum class does not enable | by default; overload operators or use std::to_underlying (C++23) / static_cast for masks.
Implementing bitwise operators
enum class Permission : uint32_t {
None = 0,
Read = 1 << 0,
Write = 1 << 1,
Execute = 1 << 2,
Delete = 1 << 3
};
// Overload operators for type-safe bit manipulation
constexpr Permission operator|(Permission lhs, Permission rhs) {
return static_cast<Permission>(
static_cast<uint32_t>(lhs) | static_cast<uint32_t>(rhs)
);
}
constexpr Permission operator&(Permission lhs, Permission rhs) {
return static_cast<Permission>(
static_cast<uint32_t>(lhs) & static_cast<uint32_t>(rhs)
);
}
constexpr Permission operator~(Permission p) {
return static_cast<Permission>(~static_cast<uint32_t>(p));
}
constexpr bool hasPermission(Permission flags, Permission check) {
return (flags & check) == check;
}
// Usage
Permission userPerms = Permission::Read | Permission::Write;
if (hasPermission(userPerms, Permission::Read)) {
// Can read
}
Real-world use cases
1. State machines
enum class ConnectionState {
Disconnected,
Connecting,
Connected,
Reconnecting,
Failed
};
class NetworkClient {
ConnectionState state_ = ConnectionState::Disconnected;
public:
void connect() {
if (state_ == ConnectionState::Disconnected) {
state_ = ConnectionState::Connecting;
// ....connection logic
}
}
bool isConnected() const {
return state_ == ConnectionState::Connected;
}
std::string_view stateString() const {
switch (state_) {
case ConnectionState::Disconnected: return "Disconnected";
case ConnectionState::Connecting: return "Connecting";
case ConnectionState::Connected: return "Connected";
case ConnectionState::Reconnecting: return "Reconnecting";
case ConnectionState::Failed: return "Failed";
}
return "Unknown";
}
};
2. Configuration options
enum class LogLevel : uint8_t {
Trace,
Debug,
Info,
Warning,
Error,
Fatal
};
class Logger {
LogLevel minLevel_ = LogLevel::Info;
public:
void setLevel(LogLevel level) { minLevel_ = level; }
template<typename....Args>
void log(LogLevel level, Args&&....args) {
if (level >= minLevel_) {
// Output log message
}
}
};
// Usage
Logger logger;
logger.setLevel(LogLevel::Debug);
logger.log(LogLevel::Info, "Application started");
3. Error codes
enum class ErrorCode : int32_t {
Success = 0,
InvalidArgument = 1,
FileNotFound = 2,
PermissionDenied = 3,
NetworkError = 4,
Timeout = 5
};
struct Result {
ErrorCode code;
std::string message;
explicit operator bool() const {
return code == ErrorCode::Success;
}
};
Result openFile(const std::string& path) {
if (path.empty()) {
return {ErrorCode::InvalidArgument, "Path is empty"};
}
// ....file opening logic
return {ErrorCode::Success, ""};
}
Common pitfalls and solutions
Pitfall 1: Forgetting scope
enum class Color { Red, Green, Blue };
// ❌ Error: 'Red' not declared
Color c = Red;
// ✅ Correct: use scope
Color c = Color::Red;
Pitfall 2: Implicit conversion expectations
enum class Status { OK, Error };
// ❌ Error: no implicit conversion
int x = Status::OK;
// ✅ Explicit cast
int x = static_cast<int>(Status::OK);
// Better: use std::to_underlying (C++23)
int x = std::to_underlying(Status::OK);
Pitfall 3: Switch without all cases
enum class State { Idle, Running, Paused, Stopped };
void handleState(State s) {
switch (s) {
case State::Idle: break;
case State::Running: break;
// ⚠️ Missing Paused and Stopped
}
}
Fix: Enable compiler warnings:
# GCC/Clang
-Wswitch-enum # Warn on missing cases
# MSVC
/Wall # Enables C4062 warning
Performance considerations
Memory layout: Underlying type controls size:
enum class Tiny : uint8_t { A, B, C }; // 1 byte
enum class Normal { A, B, C }; // int (4 bytes on most platforms)
enum class Large : uint64_t { A, B, C }; // 8 bytes
Benchmark (GCC 13, -O3):
| Type | Size | Switch performance |
|---|---|---|
enum | 4 bytes | Identical |
enum class | 4 bytes | Identical |
enum class : uint8_t | 1 byte | Identical |
Key insight: enum class has zero runtime overhead compared to enum. The type safety is compile-time only. |
Serialization patterns
JSON conversion
#include <nlohmann/json.hpp>
enum class Priority { Low, Medium, High };
NLOHMANN_JSON_SERIALIZE_ENUM(Priority, {
{Priority::Low, "low"},
{Priority::Medium, "medium"},
{Priority::High, "high"}
})
// Usage
nlohmann::json j = Priority::High; // "high"
Priority p = j.get<Priority>();
String conversion helper
template<typename E>
struct EnumTraits;
template<>
struct EnumTraits<Color> {
static constexpr std::array names = {"Red", "Green", "Blue"};
static std::string_view toString(Color c) {
return names[static_cast<size_t>(c)];
}
static std::optional<Color> fromString(std::string_view s) {
for (size_t i = 0; i < names.size(); ++i) {
if (names[i] == s) {
return static_cast<Color>(i);
}
}
return std::nullopt;
}
};
Compiler support
| Compiler | Version | enum class | enum class : type |
|---|---|---|---|
| GCC | 4.4+ | ✅ | ✅ |
| Clang | 2.9+ | ✅ | ✅ |
| MSVC | 2012+ | ✅ | ✅ |
C++23 addition: std::to_underlying simplifies casting: |
// C++11-20
auto val = static_cast<std::underlying_type_t<Status>>(Status::OK);
// C++23
auto val = std::to_underlying(Status::OK);
Related posts
Keywords
C++, enum class, scoped enum, strong typing, C++11, type safety, bit flags, state machine
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. Strongly typed scoped enums in C++11: no implicit int conversion, explicit underlying types, switch hygiene, and bit fla… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ nullptr | ‘널 포인터’ 가이드
- C++ Range Algorithms | ‘범위 알고리즘’ 가이드
- C++ constexpr Lambda | ‘컴파일 타임 람다’ 가이드
이 글에서 다루는 키워드 (관련 검색어)
C++, enum, enum class, C++11 등으로 검색하시면 이 글이 도움이 됩니다.