閉じる

【C++】enum classの使い方とメリット|従来のenumとの違いを徹底解説

C++で定数の集合を扱う際、古くから使われているenum(列挙型)には、名前の衝突や型安全性の欠如といったいくつかの課題がありました。

これらの問題を解決し、より安全で堅牢なコードを書くために導入されたのがenum class(スコープ付き列挙型)です。

本記事では、C++におけるenum classの基本的な使い方から、従来のenumとの違い、そしてC++20以降で追加された便利な機能までを詳しく解説します。

enum classとは何か

C++11から導入されたenum classは、従来の列挙型が抱えていた「グローバルな名前空間を汚染する」という問題や「暗黙的に整数へ変換されてしまう」という弱点を克服した、現代的な列挙型です。

スコープ付き列挙型の基本構造

enum classを定義する際は、以下のように記述します。

列挙子にアクセスするためには、必ず型名による修飾が必要になります。

C++
#include <iostream>

// enum classの定義
enum class Color {
    Red,
    Green,
    Blue
};

int main() {
    // 列挙子を使用する際は「型名::列挙子」の形式で記述する
    Color myColor = Color::Red;

    if (myColor == Color::Red) {
        std::cout << "選択された色は赤です。" << std::endl;
    }

    return 0;
}
実行結果
選択された色は赤です。

従来のenum(列挙型)との決定的な違い

従来のenumenum classには、大きく分けて3つの違いがあります。

これらを理解することで、なぜ現代のC++開発ではenum classが推奨されるのかが明確になります。

1. 列挙子のスコープ(名前の衝突防止)

従来のenumでは、定義された列挙子がその周囲のスコープに直接公開されてしまいます。

これにより、別の列挙型で同じ名前の定数を使おうとすると、名前の衝突が発生してコンパイルエラーになります。

C++
// 従来のenumの場合(エラーになる例)
enum TrafficLight {
    Green,
    Yellow,
    Red
};

// ここでコンパイルエラー:'Red' や 'Green' が既に定義されているため
// enum Color {
//     Red,
//     Green,
//     Blue
// };

対して、enum classでは、列挙子は型名のスコープ内に閉じ込められます。

そのため、異なる型であれば同じ名前の列挙子を定義しても問題ありません。

C++
// enum classの場合(正常に動作する)
enum class TrafficLight {
    Green,
    Yellow,
    Red
};

enum class Color {
    Red,    // TrafficLight::Red と衝突しない
    Green,  // TrafficLight::Green と衝突しない
    Blue
};

2. 型安全性(暗黙の型変換の禁止)

従来のenumは、暗黙的にint型へ変換されます。

これは便利な反面、意図しない比較を許してしまう原因になります。

例えば、信号機の「赤」と色の「赤」を比較すると、どちらも内部的に「0」や「2」といった数値として扱われるため、真と判定されてしまいます。

C++
// 従来のenum:意図しない比較が通ってしまう
enum TrafficLight { TL_Red };
enum Color { C_Red };

TrafficLight light = TL_Red;
Color color = C_Red;

if (light == color) { // コンパイルが通ってしまい、真になる可能性がある
    // 本来、信号と色は別物なので比較できるべきではない
}

enum classでは、整数型への暗黙的な変換が行われません。

これにより、論理的に誤った比較をコンパイル時点で防ぐことができます。

C++
enum class TrafficLight { Red };
enum class Color { Red };

TrafficLight light = TrafficLight::Red;
Color color = Color::Red;

// if (light == color) { } // コンパイルエラー:型が異なるため比較できない

3. 基底型の指定

enum classでは、列挙子の値を保持するためのメモリサイズ(基底型)を明示的に指定できます。

これにより、構造体のサイズを節約したり、前方宣言を確実に行ったりすることが可能になります。

C++
// 1バイト(char相当)のサイズで定義する
enum class DeviceStatus : unsigned char {
    Off = 0,
    On = 1,
    Error = 255
};

enum classの応用的な使い方

基本的な使い方に加えて、実務でよく利用されるテクニックをいくつか紹介します。

整数型への明示的なキャスト

enum classは暗黙的に整数へ変換されませんが、static_castを用いることで明示的に変換できます。

また、C++23からはstd::to_underlyingという便利な関数も追加されています。

C++
#include <iostream>
#include <utility> // std::to_underlying (C++23)

enum class ErrorCode {
    None = 0,
    NotFound = 404,
    ServerError = 500
};

int main() {
    ErrorCode code = ErrorCode::NotFound;

    // 1. 従来のキャスト方法
    int intCode1 = static_cast<int>(code);
    
    // 2. C++23以降の推奨される方法
    // 基底型を自動的に判別して変換してくれる
    auto intCode2 = std::to_underlying(code);

    std::cout << "エラーコード: " << intCode2 << std::endl;
    return 0;
}

switch文での活用

列挙型はswitch文と非常に相性が良いです。

enum classを使用する場合も、すべての列挙子を網羅することで、将来的に列挙子が増えた際の修正漏れを防ぐことができます(コンパイラの警告機能を活用)。

C++
enum class State {
    Idle,
    Running,
    Finished
};

void printState(State s) {
    switch (s) {
        case State::Idle:
            std::cout << "待機中..." << std::endl;
            break;
        case State::Running:
            std::cout << "実行中..." << std::endl;
            break;
        case State::Finished:
            std::cout << "完了しました。" << std::endl;
            break;
        // defaultをあえて書かないことで、列挙子が追加された際に警告を出せる
    }
}

C++20:using enum による記述の簡略化

enum classの唯一の弱点は、アクセスするたびに型名::と書かなければならないため、コードが冗長になりやすい点でした。

C++20で導入されたusing enumを使用すると、特定のスコープ内だけで型名を省略できるようになります。

C++
enum class Fruit { Apple, Orange, Banana };

void processFruit(Fruit f) {
    // このスコープ内では Fruit:: を省略可能
    using enum Fruit;

    switch (f) {
        case Apple:  // Fruit::Apple と書かなくて良い
            std::cout << "リンゴです" << std::endl;
            break;
        case Orange:
            std::cout << "オレンジです" << std::endl;
            break;
        case Banana:
            std::cout << "バナナです" << std::endl;
            break;
    }
}

enum classを使うメリットのまとめ

ここまでの内容を踏まえ、enum classを導入するメリットを整理します。

項目内容メリット
名前の競合回避型名によるスコープを持つ似たような定数名(Red, Blue等)を複数の場所で使える
型安全性の向上暗黙の整数変換を禁止異なる意味を持つ値との誤った比較をコンパイル時に検知できる
メモリの最適化基底型の明示的な指定必要なビット数に合わせてメモリ使用量を最小限に抑えられる
可読性の向上Type::Value 形式の記述その定数がどのグループに属しているかがコードから一目でわかる

まとめ

enum classは、従来の列挙型が抱えていた「グローバルスコープの汚染」と「緩い型チェック」という2つの大きな問題を解決した、非常に強力なツールです。

現代のC++開発において、特別な理由(レガシーなC言語ライブラリとの互換性維持など)がない限り、列挙型を定義する際は常にenum classを選択すべきと言えます。

名前の衝突を防ぎ、型安全性を高めることは、プログラムのバグを減らし、メンテナンス性を向上させるための第一歩です。

C++20のusing enumなどの新機能も活用しながら、よりクリーンで安全なコードを記述していきましょう。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!