본문으로 건너뛰기
Previous
Next
C++ enum class | Scoped Enumerations Explained

C++ enum class | Scoped Enumerations Explained

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

Featureenumenum class
Implicit int conversionYesNo
Scoped enumeratorsNoYes
Name clashesCommonRare

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):

TypeSizeSwitch performance
enum4 bytesIdentical
enum class4 bytesIdentical
enum class : uint8_t1 byteIdentical
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

CompilerVersionenum classenum class : type
GCC4.4+
Clang2.9+
MSVC2012+
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);

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++, enum, enum class, C++11 등으로 검색하시면 이 글이 도움이 됩니다.