C++ コア ガイドライン:列挙の規則

列挙のセクションには 8 つのルールがあります。 C++11 以降、従来の列挙型の多くの欠点を克服する列挙型のスコープがありました。

列挙は、型のように動作する整数値のセットです。ルールの概要は次のとおりです:

  • Enum.1:マクロよりも列挙を好む
  • Enum.2:列挙を使用して、関連する名前付き定数のセットを表す
  • Enum.3:enum class を優先 「プレーン」 enum の es
  • Enum.4:安全で簡単に使用できるように列挙に対する操作を定義する
  • Enum.5:ALL_CAPS を使用しないでください 列挙子用
  • Enum.6:名前のない列挙を避ける
  • Enum.7:必要な場合にのみ列挙型の基になる型を指定します
  • Enum.8:必要な場合にのみ列挙値を指定する

この投稿の冒頭で述べたように、従来の列挙型には多くの欠点があります。この重要な比較は規則で明示的に記述されていないため、従来の (スコープのない) 列挙とスコープ付きの列挙 (厳密に型指定された列挙と呼ばれることもあります) を明示的に比較してみましょう。

以下は古典的な列挙です:

enum Colour{
 red,
 blue,
 green
};

従来の列挙型の欠点は次のとおりです。

  • 列挙子にはスコープがありません
  • 列挙子は暗黙的に int に変換されます
  • 列挙子はグローバル名前空間を汚染します
  • 列挙子の型が定義されていません。列挙子を保持するのに十分な大きさである必要があります。

キーワード class または struct を使用することにより、従来の列挙はスコープ付き列挙 (enum クラス) になります。

enum class ColourScoped{
 red,
 blue,
 green
};

ここで、列挙子にアクセスするためにスコープ演算子 ColourScoped::red を使用する必要があります。 ColourScoped::red は暗黙的に int に変換しないため、グローバル名前空間を汚染しません。さらに、基になる型はデフォルトの int ごとです。

背景情報を提供した後、ルールに直接ジャンプできます。

Enum.1:マクロよりも列挙を優先

マクロはスコープを尊重せず、型を持ちません。これは、色を指定する以前に設定されたマクロをオーバーライドできることを意味します。

// webcolors.h 
#define RED 0xFF0000

// productinfo.h
#define RED 0

int webcolor = RED; // should be 0xFF0000

ColourScoped では、スコープ演算子を使用する必要があるため、これは発生しません:ColourScoped webcolour =ColourScoped::red;

列挙子は一種の型を作成する整数のセットであるため、この規則は非常に明白です。

Enum.3:enum class を優先 「プレーン」 enum の es さ

スコープ付き列挙型 (列挙型クラス) の列挙子は、自動的に int に変換されません。スコープ演算子でそれらにアクセスする必要があります。

// scopedEnum.cpp

#include <iostream>

enum class ColourScoped{
 red,
 blue,
 green
};

void useMe(ColourScoped color){

 switch(color){
 case ColourScoped::red:
 std::cout << "ColourScoped::red" << std::endl;
 break;
 case ColourScoped::blue:
 std::cout << "ColourScoped::blue" << std::endl;
 break;
 case ColourScoped::green:
 std::cout << "ColourScoped::green" << std::endl;
 break;
 }
}

int main(){

 std::cout << static_cast<int>(ColourScoped::red) << std::endl; // 0
 std::cout << static_cast<int>(ColourScoped::red) << std::endl; // 0

 std::cout << std::endl;

 ColourScoped colour{ColourScoped::red};
 useMe(colour); // ColourScoped::red

}

Enum.4:安全で簡単な使用のために列挙型の操作を定義する

ルールは、インクリメント操作をサポートする列挙 Day を定義します。

enum Day { mon, tue, wed, thu, fri, sat, sun };

Day& operator++(Day& d)
{
 return d = (d == Day::sun) ? Day::mon : static_cast<Day>(static_cast<int>(d)+1);
}

Day today = Day::sat;
Day tomorrow = ++today;

インクリメント演算子内にインクリメント演算子を適用すると無限再帰が発生するため、この例では static_cast が必要です:

Day& operator++(Day& d)
{
 return d = (d == Day::sun) ? Day::mon : Day{++d}; // error
}

Enum.5:ALL_CAPS を使用しないでください 列挙子用

列挙子に ALL_CAPS を使用すると、マクロは通常 ALL_CAPS で記述されるため、競合が発生する可能性があります。

#define RED 0xFF0000

enum class ColourScoped{ RED }; // error

Enum.6:名前のない列挙を避ける

列挙の名前が見つからない場合、それらの列挙は関連していない可能性があります。この場合、constexpr 値を使用する必要があります。

// bad
enum { red = 0xFF0000, scale = 4, is_signed = 1 };

// good
constexpr int red = 0xFF0000;
constexpr short scale = 4;
constexpr bool is_signed = true;

Enum.7:必要な場合にのみ、列挙型の基になる型を指定します

C++11 以降、列挙型の基になる型を指定してメモリを節約できます。デフォルトでは、スコープ付き列挙型の型は int であるため、列挙型を前方宣言できます。

// typeEnum.cpp

#include <iostream>

enum class Colour1{
 red,
 blue,
 green
};
 
enum struct Colour2: char {
 red,
 blue,
 green
};

int main(){

 std::cout << sizeof(Colour1) << std::endl; // 4
 std::cout << sizeof(Colour2) << std::endl; // 1

}

Enum.8:必要な場合にのみ列挙値を指定する

列挙子の値を指定すると、値を 2 回設定する場合があります。次の列挙 Col2 にはこの問題があります。

enum class Col1 { red, yellow, blue };
enum class Col2 { red = 1, yellow = 2, blue = 2 }; // typo
enum class Month { jan = 1, feb, mar, apr, may, jun,
 jul, august, sep, oct, nov, dec }; // starting with 1 is conventional

次は?

この投稿では比較的短くしました。覚えておくべきメタルールは次のとおりです:範囲指定された列挙型を使用する .

C++ コア ガイドラインの次のセクションでは、リソース管理の約 35 のルールを扱います。これは、次の投稿で C++ の核心に飛び込むことを意味します。