型安全性のために、C++でenum classes を使用することを推奨する人もいます。
しかし、それは本当にどういう意味ですか?
C++には2種類のenum
があります。
enum class
esenum
s宣言方法の例をいくつか示します。
enum class Color { red, green, blue }; // enum class
enum Animal { dog, cat, bird, human }; // plain enum
両者の違いは何ですか?
enum class
es - 列挙子名は local に列挙型であり、それらの値はnot暗黙のうちに(他のenum
やint
のように)変換します)
プレーンなenum
s - 列挙子の名前はenumと同じスコープ内にあり、それらの値は暗黙的に整数やその他の型に変換されます
例:
enum Color { red, green, blue }; // plain enum
enum Card { red_card, green_card, yellow_card }; // another plain enum
enum class Animal { dog, deer, cat, bird, human }; // enum class
enum class Mammal { kangaroo, deer, human }; // another enum class
void fun() {
// examples of bad use of plain enums:
Color color = Color::red;
Card card = Card::green_card;
int num = color; // no problem
if (color == Card::red_card) // no problem (bad)
cout << "bad" << endl;
if (card == Color::green) // no problem (bad)
cout << "bad" << endl;
// examples of good use of enum classes (safe)
Animal a = Animal::deer;
Mammal m = Mammal::deer;
int num2 = a; // error
if (m == a) // error (good)
cout << "bad" << endl;
if (a == Mammal::deer) // error (good)
cout << "bad" << endl;
}
enum class
esは、バグにつながる可能性がある驚きの数が少ないため、推奨されます。
からBjarne StroustrupのC++ 11 FAQ :
enum class
es( "new enums"、 "strong enums")は、伝統的なC++の列挙に関する3つの問題を扱います。
- 従来の列挙型は暗黙的にint型に変換されるため、列挙型を整数として機能させたくない場合にエラーが発生します。
- 従来の列挙型は列挙子を周囲のスコープにエクスポートするため、名前が衝突します。
- 基礎となる
enum
の型は指定できないため、混乱、互換性の問題、そして前方宣言は不可能になります。新しい列挙型は「列挙型クラス」です。なぜなら、それらは従来の列挙型(名前値)の側面とクラスの側面(スコープ付きメンバーおよび変換の欠如)を兼ね備えているからです。
したがって、他のユーザーが述べたように、「強力な列挙型」はコードをより安全にします。
"古典的な" enum
の基本型は、enum
のすべての値に適合するのに十分な大きさの整数型でなければなりません。これは通常int
です。また、各列挙型はchar
または符号付き/符号なし整数型と互換性があります。
これはenum
の基本型がどうあるべきかについての広範な説明です。そのため、各コンパイラは古典的なenum
の基本型について自分で決定を下しますが、結果が驚くこともあります。
たとえば、私はこのようなコードを何度も見ました。
enum E_MY_FAVOURITE_FRUITS
{
E_Apple = 0x01,
E_WATERMELON = 0x02,
E_COCONUT = 0x04,
E_STRAWBERRY = 0x08,
E_CHERRY = 0x10,
E_PINEAPPLE = 0x20,
E_BANANA = 0x40,
E_MANGO = 0x80,
E_MY_FAVOURITE_FRUITS_FORCE8 = 0xFF // 'Force' 8bits, how can you tell?
};
上のコードでは、素朴なコーダーがE_MY_FAVOURITE_FRUITS
の値を符号なし8bitの型に格納すると考えていますが、それについての保証はありません。コンパイラはunsigned char
またはint
またはshort
を選択できます。 enum
に見られるすべての値に合うように。フィールドE_MY_FAVOURITE_FRUITS_FORCE8
を追加することは負担であり、コンパイラがenum
の基になる型について何らかの選択をすることを強制しません。
型のサイズに依存しているコードや、E_MY_FAVOURITE_FRUITS
の幅がある程度広いと想定しているコード(例:シリアル化ルーチン)がある場合、このコードはコンパイラの考えによっては奇妙な方法で動作することがあります。
さらに悪いことに、何人かの同僚が私たちのenum
に不用意に新しい値を追加した場合、
E_DEVIL_FRUIT = 0x100, // New fruit, with value greater than 8bits
コンパイラはそれについて文句を言わない! enum
のすべての値に合うように型のサイズを変更するだけです(コンパイラが可能な限り小さい型を使用していたと仮定します。これは、できないという仮定です)。 enum
へのこの単純で不注意な追加は、関連するコードを微妙に壊す可能性があります。
C++ 11ではenum
およびenum class
に基本型を指定することができるので(Thanks rdb )、この問題はきちんと対処されています。
enum class E_MY_FAVOURITE_FRUITS : unsigned char
{
E_Apple = 0x01,
E_WATERMELON = 0x02,
E_COCONUT = 0x04,
E_STRAWBERRY = 0x08,
E_CHERRY = 0x10,
E_PINEAPPLE = 0x20,
E_BANANA = 0x40,
E_MANGO = 0x80,
E_DEVIL_FRUIT = 0x100, // Warning!: constant value truncated
};
フィールドがこの型の範囲外の式を持つ場合、基本型を指定すると、コンパイラは基本型を変更する代わりに文句を言います。
これは安全性の改善につながると思います。
だから なぜ単純なenumよりenumクラスが好まれるのか? 、スコープ付き(enum class
)およびスコープなし(enum
)の基本型を選択できる場合、他に何がenum class
をより良い選択にするのでしょうか。
int
に変換することはありません。通常のenumよりもenumクラスを使用することの基本的な利点は、2つの異なるenumに対して同じenum変数を持つことができ、それでも解決できることです(これは type safe OPとして言及されています)。
例えば:
enum class Color1 { red, green, blue }; //this will compile
enum class Color2 { red, green, blue };
enum Color1 { red, green, blue }; //this will not compile
enum Color2 { red, green, blue };
基本的な列挙型に関しては、コンパイラは、以下のステートメントのようにred
がColor1
またはColor2
型を参照しているかどうかを区別できません。
enum Color1 { red, green, blue };
enum Color2 { red, green, blue };
int x = red; //Compile time error(which red are you refering to??)
列挙型は一連の整数値を表すために使用されます。
class
の後のenum
キーワードは、列挙が厳密に型指定され、その列挙が有効範囲であることを指定します。このようにしてenum
クラスは定数の偶然の誤用を防ぎます。
例えば:
enum class Animal{Dog, Cat, Tiger};
enum class Pets{Dog, Parrot};
ここでは動物とペットの値を混在させることはできません。
Animal a = Dog; // Error: which DOG?
Animal a = Pets::Dog // Pets::Dog is not an Animal
他の回答で述べたように、クラスenumは暗黙的にint/boolに変換できないので、それはバグの多いコードを避けるのにも役立ちます:
enum MyEnum {
Value1,
Value2,
};
...
if (var == Value1 || Value2) // Should be "var == Value2" no error/warning
C++ 11 FAQ 以下の点に言及します。
従来の列挙型は暗黙的にint型に変換されるため、列挙型を整数として使用したくない場合にエラーが発生します。
enum color
{
Red,
Green,
Yellow
};
enum class NewColor
{
Red_1,
Green_1,
Yellow_1
};
int main()
{
//! Implicit conversion is possible
int i = Red;
//! Need enum class name followed by access specifier. Ex: NewColor::Red_1
int j = Red_1; // error C2065: 'Red_1': undeclared identifier
//! Implicit converison is not possible. Solution Ex: int k = (int)NewColor::Red_1;
int k = NewColor::Red_1; // error C2440: 'initializing': cannot convert from 'NewColor' to 'int'
return 0;
}
従来のenumは列挙子を周囲のスコープにエクスポートするため、名前が衝突します。
// Header.h
enum vehicle
{
Car,
Bus,
Bike,
Autorickshow
};
enum FourWheeler
{
Car, // error C2365: 'Car': redefinition; previous definition was 'enumerator'
SmallBus
};
enum class Editor
{
vim,
eclipes,
VisualStudio
};
enum class CppEditor
{
eclipes, // No error of redefinitions
VisualStudio, // No error of redefinitions
QtCreator
};
基になる列挙型を指定できないため、混乱、互換性の問題、および前方宣言が不可能になります。
// Header1.h
#include <iostream>
using namespace std;
enum class Port : unsigned char; // Forward declare
class MyClass
{
public:
void PrintPort(enum class Port p);
};
void MyClass::PrintPort(enum class Port p)
{
cout << (int)p << endl;
}
。
// Header.h
enum class Port : unsigned char // Declare enum type explicitly
{
PORT_1 = 0x01,
PORT_2 = 0x02,
PORT_3 = 0x04
};
。
// Source.cpp
#include "Header1.h"
#include "Header.h"
using namespace std;
int main()
{
MyClass m;
m.PrintPort(Port::PORT_1);
return 0;
}
明示的に言及されていないことの一つ - スコープ機能はあなたに列挙型とクラスメソッドのために同じ名前を持つためのオプションを与えます。例えば:
class Test
{
public:
// these call ProcessCommand() internally
void TakeSnapshot();
void RestoreSnapshot();
private:
enum class Command // wouldn't be possible without 'class'
{
TakeSnapshot,
RestoreSnapshot
};
void ProcessCommand(Command cmd); // signal the other thread or whatever
};