C++の列挙体ガイド:enum から C++11 enum class への進化

目次

列挙体 (enum) とは?

プログラミングにおいて、012 のような数値(いわゆる「マジックナンバー」)を直接コードに記述すると、その数値が何を意味するのかが分かりにくくなります。

C++の**列挙体(enum)**は、Status::Pending (0)、Status::Active (1)、Status::Error (2) のように、関連する一連の整数定数に分かりやすい名前を付けるための、ユーザー定義型です。

これにより、コードの可読性と保守性が大幅に向上します。

現代の主流:C++11 enum class(スコープ付き列挙体)

伝統的なC言語スタイルの enum には、いくつかの問題点がありました。

  1. スコープ汚染: 列挙子(Pendingなど)の名前が、enumを定義したスコープに直接属してしまうため、他のenumや変数と名前が衝突する可能性がありました。
  2. 暗黙的なintへの変換: enum型からint型へ暗黙的に変換できてしまうため、型安全性が低いという問題がありました。

C++11では、これらの問題を解決した「enum class」(スコープ付き列挙体)が導入され、現在はこちらの使用が強く推奨されます。

enum class の主なメリット

  1. スコープが限定される: 列挙子は enum class の名前空間内にカプセル化されます。AppStatus::Pending のように、型名::列挙子 という形でアクセスする必要があり、名前の衝突を防ぎます。
  2. 型安全性が高い: int への暗黙的な変換が禁止されます。int に変換したい場合は、static_cast を使って明示的に行う必要があります。

enum class の基本的な使い方

enum class を使って、アプリケーションの動作モードを定義する例を見てみましょう。

#include <iostream>
#include <string>

// アプリケーションの動作モードを定義するスコープ付き列挙体
enum class OperationMode {
    Normal,     // 通常モード
    Safe,       // セーフモード
    Maintenance // メンテナンスモード
};

// 動作モードに基づいて説明文を返す関数
std::string getModeDescription(OperationMode mode) {
    // switch文で列挙子を判定する
    switch (mode) {
        case OperationMode::Normal:
            return "通常モードで動作中です。";
        case OperationMode::Safe:
            return "セーフモードで動作中です。機能が制限されています。";
        case OperationMode::Maintenance:
            return "メンテナンスモードです。管理タスクを実行しています。";
        default:
            return "不明なモードです。";
    }
}

int main() {
    // 列挙体型の変数を宣言し、初期化
    OperationMode currentMode = OperationMode::Maintenance;

    std::cout << getModeDescription(currentMode) << std::endl;

    // 別のモードに変更
    currentMode = OperationMode::Normal;
    std::cout << getModeDescription(currentMode) << std::endl;

    return 0;
}

実行結果:

メンテナンスモードです。管理タスクを実行しています。
通常モードで動作中です。

enum と整数の変換

enum class は型安全性が高いため、整数との変換には明示的なキャストが必要です。

int から enum class への変換

ユーザーからの入力(0, 1, 2など)を enum class に変換する場合、static_cast を使用します。

#include <iostream>

enum class OperationMode {
    Normal = 0, // 0
    Safe = 1,   // 1
    Maintenance = 2 // 2
};

int main() {
    int userInput;
    std::cout << "モードを選択してください (0:通常, 1:セーフ, 2:メンテ): ";
    std::cin >> userInput;

    // 入力値が有効範囲内かチェック (本来はより厳密なチェックが必要)
    if (userInput >= 0 && userInput <= 2) {
        
        // int から OperationMode へ明示的にキャスト
        OperationMode selectedMode = static_cast<OperationMode>(userInput);

        if (selectedMode == OperationMode::Safe) {
            std::cout << "セーフモードが選択されました。" << std::endl;
        } else {
            std::cout << "セーフモード以外が選択されました。" << std::endl;
        }
    } else {
        std::cout << "無効な選択です。" << std::endl;
    }

    return 0;
}

enum class から int への変換

enum の値を int 型の変数に格納したり、数値として表示したりする場合も static_cast が必要です。

OperationMode currentMode = OperationMode::Maintenance;

// OperationMode から int へ明示的にキャスト
int modeValue = static_cast<int>(currentMode);

std::cout << "メンテナンスモードの内部値は " << modeValue << " です。" << std::endl;
// 出力: メンテナンスモードの内部値は 2 です。

enum class は、デフォルトで int を基底(内部的な)型として使用しますが、enum class MyEnum : uint8_t { ... } のように、charunsigned int などの他の整数型を明示的に指定することも可能です。

(参考)名前のない列挙体

伝統的な enum には、型名を付けずに定数だけを定義する「名前のない列挙体」という用法もありました。

// 伝統的なC言語スタイルの
// 名前なし enum
enum {
    MaxConnection = 100,
    DefaultPort = 8080
};

// MaxConnection は 100 という int 定数として扱われる
int port = DefaultPort;

これは、const キーワードが一般化する前に、コンパイル時定数を作成するテクニックとして使われていました。

現代のC++では、この目的のためには const または constexpr を使用する方がはるかに明確です。

// 現代のC++での推奨される代替手段
constexpr int MaxConnection = 100;
constexpr int DefaultPort = 8080;

列挙体を利用するメリット(まとめ)

列挙体、特に enum class を利用することには、以下の大きなメリットがあります。

  1. 可読性: if (mode == 1) よりも if (mode == OperationMode::Safe) の方が、コードの意図が即座に理解できます。
  2. 型安全性: enum class を使うことで、OperationMode 型の変数に LogLevel 型の値や、123 のような無関係な整数が誤って代入されることをコンパイラが防いでくれます。
  3. 保守性: 関連する定数が一つの enum class にまとまるため、管理が容易になります。

C++11以降では、enum class を積極的に使用することで、より安全で読みやすいコードを記述することが推奨されます。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次