⚖️

enum VS enum class

2024/10/05に公開

最近見たコードでenumの代わりに C++11 で導入されたenum classが使われているのを見て嬉しかったです。

enumenum classを比較すると、enum classの方がstrong type、つまり型のチェックが厳しいため、もっと安全です。

enum

enum OS
{
    Windows = 0,
    Linux = 1,
    Mac = 2,
};

enumint型をベースにしていて、intに暗黙的に変換されるため、そのまま数字として使えます。例えば配列のインデックスとして使えますし、for ループでも回せます。とにかく使いやすいですが、悪く言えば、ただの特定の名前が付いているint型の変数なのです。

例えば、上記のOSというenum型を期待している関数に全然違うenum型の変数を渡しても、コンパイルエラーにならないです。

enum OS
{
    Windows = 0,
    Linux = 1,
    Mac = 2,
};

enum Office
{
    Office365 = 0,
    OpenOffice = 1,
    LibreOffice = 2,
};

enum OS x = Windows;
x = OpenOffice;     // コンパイルは通ってしまう

enum class

enum class OS : short
{
    Windows = 0,
    Linux = 1,
    Mac,
};

enum classはベース型を定義できて、ベース型には明示的にしか変換できないです。上記の例で、OS型を期待している関数に違うenum class型の変数を渡すと、ちゃんとコンパイルエラーになります。

enum class OS : short
{
    Windows = 0,
    Linux = 1,
    Mac,
};

enum class Office : short
{
    Office365 = 0,
    OpenOffice = 1,
    LibreOffice,
};

OS os = OS::Windows;
os = Office::Office365;     // コンパイルエラー!

でも型のチェックに厳しいからこそ、enum classは正直使いづらいです。

例えば、enum class型の変数を出力したい時は、毎回明示的にキャストしないとできないです。
でもキャストする時に注意してください。上記のenum classの場合は、もちろん以下のように直接shortにキャストできます。

const OS platform = OS::Windows;
std::cout << "platform: " << static_cast<short>(platform) << std::endl;

でもこうすると、OSのベース型を変えると、キャストしている箇所も全部一緒に変えないといけないため、本当は以下のようにキャストした方がいいです。

#include <type_traits>
std::cout << "platform: " << static_cast<std::underlying_type<OS>::type>(platform) << std::endl;

でもこれだと長くて読みづらいため、ユーティリティ関数を作るか以下のように関数テンプレートを作っておくといいです。

#include <type_traits>
template <typename Enumeration>
auto to_number(Enumeration const value) -> typename std::underlying_type<Enumeration>::type
{
    return static_cast<typename std::underlying_type<Enumeration>::type>(value);
}

以下のように使えます。

std::cout << "platform: " << to_number<OS>(platform) << std::endl;

でも本来は C++ 標準ライブラリがこういう関数を提供するべきです。と思ったら、C++23 で以下の関数が追加されるようですね。

#include <utility>
std::cout << "platform: " << std::to_underlying(platform) << std::endl;

これだったら、もう少し使いやすくなりますね。でも C++23 か… かなり先の話ですね…

後、注意ですが、せっかくenum classを使っているのに、毎回ベース型にキャストしていると、ただのenumに成り下がってしまいます。そういう使い方が必要でしたら、enum classを使う意味がなくなってしまうでしょう。場合によって、enumを使う方が良かったりします。


|cpp記事一覧へのリンク|

Discussion