본문으로 건너뛰기 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


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.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers C++, enum, enum class, C++11.