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
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. Strongly typed scoped enums in C++11: no implicit int conversion, explicit underlying types, switch hygiene, and bit fla…
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence. See the C++ series index for the full picture.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- C++ nullptr | ‘널 포인터’ 가이드
- C++ Range Algorithms | ‘범위 알고리즘’ 가이드
- C++ constexpr Lambda | ‘컴파일 타임 람다’ 가이드
Keywords Covered in This Article (Related Search Terms)
This article covers C++, enum, enum class, C++11.