Menu

C++のenum:enum classと通常のenumの違いを解説

C++のenumを学ぼう。宣言の仕方、スコープ付きのenum classが通常のenumより安全な理由、独自の基底値、列挙子に対するswitch、そして整数との相互変換について解説します。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

enumは何のためにあるのか

structは、関連する複数の値を1つのオブジェクトにまとめます。enumは別の問題を解決します。小さくて固定された選択肢の集合に名前を付けるのです。0が「赤」、1が「緑」、2が「青」を意味すると覚える代わりに、Color::Redと書けば、コンパイラがあなたを間違えさせません。

変数が名前付きの状態のうちの1つしか取りえないとき —信号機の色、トランプのスート、接続ステータスなど— つねにenumに手を伸ばせば、コードは自己説明的になり、裸の整数では決して捕まえられないタイプミスや漏れたケースをコンパイラが捕まえてくれます。

enumを宣言する

モダンC++には2つの種類があります。ほとんどの場合に手を伸ばすべき方から始めましょう。スコープ付きenum classです。名前を列挙すると、各列挙子にはenumの名前を通して::でアクセスします:

Color::Greenと書き、決して裸のGreenとは書かないことに注目してください。値そのものは単なるラベルです。比較したり、代入したり、あちこちに渡したりしますが、背後にある数値を気にすることはめったにありません。デフォルトではRed0Green1Blue2で、ゼロから上に数えていきます。

通常のenumとenum class

より古い、スコープなしのenumclassキーワードなし)は、名前を周囲のスコープに直接ばらまき、自分でintへ変換します。便利そうに聞こえますが、2つの本当の問題を引き起こします:

enum Color { Red, Green, Blue };
enum Fruit { Apple, Banana, Red };  // error: 'Red' already declared

enum Status { Active, Inactive };
int x = Active;          // compiles silently - is this what you meant?
if (Active == Banana) {  // compares unrelated enums via int - allowed!
}

通常の列挙子はグローバルな名前なので、2つのenumがラベルを共有するだけで衝突しうります。さらにintへ成り下がるため、コンパイラはまったく無関係なenum同士の値を平気で比較します。スコープ付きのenum classは両方を解決します。名前は型の内側に存在し、型は黙ってintに化けることもありません:

経験則:デフォルトでenum classを使いましょう。古いCスタイルのフラグ定数のように、暗黙のint変換が明確に必要なときだけ、通常のenumに戻してください。

独自の値と基底型

列挙子には明示的な数値を割り当てられます。割り当てを省いたものは、前の値から続けて上に数えていきます。これはHTTPステータスコードやビットフラグのようなものに便利です:

すべてのenumは整数型に裏付けられています —デフォルトではintです。サイズが重要なとき、たとえば多数のenumを詰め込んだ構造体に格納する場合や、ワイヤーフォーマットに合わせる場合には、より小さな型に固定できます:

基底型を選ぶことは、値が収まらなければならない範囲も保証します。uint8_tのenumは255を超える値を保持できません。

enumとintの間で変換する

スコープ付きのenum classは決して暗黙的に変換されません。それこそが核心です。数値が本当に必要なとき —表示したり、配列のインデックスにしたり、ファイルから読み込んだりするとき— にはstatic_castに手を伸ばしましょう。enumからintへ向かうのは常に安全です:

intをenumへ戻す変換は危険な方向です。キャストは、その数値が実在する列挙子に対応するかをチェックしません。enumの名前付き集合の外側にある、技術的に範囲外の値を渡してきます:

Suit s = static_cast<Suit>(2);   // fine - that's Clubs
Suit bad = static_cast<Suit>(99); // compiles, but 99 is not a valid Suit
// using `bad` in a switch or as an array index is a lurking bug

整数がユーザー入力やファイルから来る場合は、キャストする前に自分で範囲を検証してください。さもないと、どのcaseも決して処理しない値を作り出してしまい、のちのち 未定義動作 という微妙な原因になります。

switchでenumを使う

enumは「固定された集合のうちの1つ」なので、switchと完璧に組み合わさります。すべての列挙子をカバーしておけば、あとで新しい値を追加して処理を忘れたときに、多くのコンパイラが警告してくれます —生の整数では得られない、ただで手に入る安全性です:

落とし穴が1つ:列挙子の名前を表示する組み込みの方法はありません。cout << TrafficLight::Redはスコープ付きenumではコンパイルできず、通常のenumであっても「Red」ではなく数値を表示します。上のような小さなswitchやルックアップテーブルが、enumを人間が読める文字列に変換する一般的な方法です。

次へ:例外

enumとstructは、データがどのような姿をしているかをモデル化できます。しかし現実のプログラムは、物事がうまくいかない場合にも対処しなければなりません —開けないファイル、解析できない数値、範囲外の値など。C++はこうした失敗の経路を例外で扱います。それが次のページのテーマです。

よくある質問

C++のenumとenum classの違いは何ですか?

通常のenumは名前を周囲のスコープに漏らし、暗黙的にintへ変換されるため、名前の衝突や意図しない比較を引き起こします。スコープ付きのenum classは名前をenumの内側に保ち(Color::Red)、明示的なキャストなしにはintへ変換しません。モダンC++ではenum classを優先しましょう。型安全であり、古典的な落とし穴を避けられます。

C++のenumをintに変換するにはどうすればよいですか?

通常のenumは暗黙的に変換されるので、int n = Red;がそのまま動きます。スコープ付きのenum classは明示的なキャストが必要です:int n = static_cast<int>(Color::Red);。逆方向に進むには、intをキャストして戻します:Color c = static_cast<Color>(2);。ただし注意してください。実行時には、その値が有効な列挙子かどうかはチェックされません。

C++の最初のenumはどの値から始まりますか?

デフォルトでは最初の列挙子は0で、それ以降は前の値より1ずつ大きくなります。したがってenum class Level { Low, Mid, High };では、Low0Mid1High2になります。この挙動を上書きするために、いずれの列挙子にも明示的な値を割り当てられます。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める