How to use enums in C++

Learn how to use enums in c++ with practical examples, diagrams, and best practices. Covers c++, enums development techniques with visual explanations.

Mastering Enums in C++: A Comprehensive Guide

Hero image for How to use enums in C++

Explore the power and flexibility of enumerations in C++. This guide covers traditional enums, enum class for type safety, and best practices for their effective use in modern C++ development.

Enumerations, or enums, are a fundamental feature in C++ that allow you to define a type whose value is restricted to a set of named constants. They enhance code readability and maintainability by replacing 'magic numbers' with meaningful names. This article will guide you through the different types of enums available in C++, their advantages, and how to use them effectively in your projects.

Traditional C-style Enums

Before C++11, only traditional C-style enums were available. These enums define a set of named integer constants. While useful, they come with certain limitations, primarily regarding type safety and scope pollution. Each enumerator implicitly converts to an int, and their names are injected directly into the enclosing scope, which can lead to naming conflicts.

enum Color {
    RED,    // 0
    GREEN,  // 1
    BLUE    // 2
};

enum TrafficLight {
    RED_LIGHT, // 0 (conflict with Color::RED if not careful)
    YELLOW,
    GREEN_LIGHT
};

void printColor(Color c) {
    switch (c) {
        case RED:
            std::cout << "It's red!" << std::endl;
            break;
        case GREEN:
            std::cout << "It's green!" << std::endl;
            break;
        case BLUE:
            std::cout << "It's blue!" << std::endl;
            break;
    }
}

int main() {
    Color myColor = BLUE;
    printColor(myColor);

    // Implicit conversion to int is allowed
    int colorValue = RED;
    std::cout << "Red value: " << colorValue << std::endl;

    // This compiles, demonstrating lack of type safety
    // Color anotherColor = 100; // Not a valid enumerator, but compiles
    // TrafficLight light = RED; // Potential naming conflict

    return 0;
}

Example of a traditional C-style enum and its usage.

Scoped Enums (enum class) for Type Safety

C++11 introduced enum class (also known as scoped enums) to address the shortcomings of traditional enums. Scoped enums provide strong type safety and prevent name collisions by encapsulating enumerators within the enum's scope. They do not implicitly convert to integers, requiring explicit casting when needed.

enum class Animal {
    DOG,
    CAT,
    BIRD
};

enum class PetStatus {
    ADOPTED,
    AVAILABLE,
    RESERVED
};

void printAnimal(Animal a) {
    switch (a) {
        case Animal::DOG:
            std::cout << "Woof!" << std::endl;
            break;
        case Animal::CAT:
            std::cout << "Meow!" << std::endl;
            break;
        case Animal::BIRD:
            std::cout << "Chirp!" << std::endl;
            break;
    }
}

int main() {
    Animal myPet = Animal::CAT;
    printAnimal(myPet);

    // This will NOT compile: implicit conversion to int is forbidden
    // int animalValue = Animal::DOG;

    // Explicit cast is required
    int animalValue = static_cast<int>(Animal::DOG);
    std::cout << "Dog value: " << animalValue << std::endl;

    // No naming conflict with other enums
    PetStatus status = PetStatus::AVAILABLE;

    return 0;
}

Demonstration of enum class providing type safety and scope encapsulation.

flowchart TD
    A[Start]
    B{Choose Enum Type}
    C[Traditional Enum]
    D[Scoped Enum (enum class)]
    E{Type Safety?}
    F{Scope Pollution?}
    G[Implicit Conversion to int?]
    H[Explicit Conversion to int?]
    I[End]

    A --> B
    B --> C
    B --> D

    C --> E
    E -- No --> F
    F -- Yes --> G
    G -- Yes --> I

    D --> E
    E -- Yes --> F
    F -- No --> H
    H -- Yes --> I

Decision flow for choosing between traditional enums and enum class.

Specifying the Underlying Type

Both traditional enums and enum class allow you to specify the underlying integer type. This can be useful for controlling memory usage or ensuring compatibility with external systems. By default, the underlying type is implementation-defined for traditional enums (large enough to hold all enumerator values) and int for enum class.

enum SmallNumbers : unsigned char {
    ZERO,
    ONE,
    TWO
};

enum class LargeValues : long long {
    MAX_INT = 2147483647LL,
    MAX_LONG = 9223372036854775807LL
};

int main() {
    SmallNumbers s = ONE;
    std::cout << "Size of SmallNumbers: " << sizeof(s) << " byte(s)" << std::endl;

    LargeValues l = LargeValues::MAX_LONG;
    std::cout << "Size of LargeValues: " << sizeof(l) << " byte(s)" << std::endl;

    return 0;
}

Specifying the underlying type for enums.