型の安全性のために、C ++で列挙型クラスを使用することを勧める人が何人かいます。
しかし、それは本当にどういう意味ですか?
型の安全性のために、C ++で列挙型クラスを使用することを勧める人が何人かいます。
しかし、それは本当にどういう意味ですか?
回答:
C ++には次の2種類がありenum
ます。
enum class
esenum
sこれらを宣言する方法の例をいくつか示します。
enum class Color { red, green, blue }; // enum class
enum Animal { dog, cat, bird, human }; // plain enum
2つの違いは何ですか?
enum class
es-列挙子の名前はenum に対してローカルであり、その値は暗黙的に他の型(別のorのような)に変換されません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は、バグの原因となる可能性のある驚きが少ないため、推奨されます。
A
state を持つクラスがあり、class enum class State { online, offline };
の子としてを作成した場合、... ではなく内部でチェックA
を実行したいのですが、それは可能ですか?state == online
A
state == State::online
enum class
それを排除することでした。
Color color = Color::red
。
if (color == Card::red_card)
行、4行後のコメントよりも(私が今見るブロックの最初の半分に適用されます。)ブロックの2行が与え悪い例を。最初の3行は問題ではありません。「ブロック全体がプレーンな列挙型が悪い理由です」と私は思いました。私は今、それは単なるセットアップです。いずれにしても、フィードバックをありがとう。
enum class
ES(「新しい列挙型」、「強い列挙型」)アドレス伝統的な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
符号なし8ビットタイプに値を...しかし、それについては保証はありません:コンパイラが選択することができ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
and の基になる型を指定できるためenum class
(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よりも優先されるのですか?、scoped(enum class
)およびunscoped(enum
)列挙型の基礎となる型を選択できる場合、他に何がenum class
より適切な選択になるでしょうか?:
int
。通常の列挙型よりも列挙型クラスを使用する場合の基本的な利点は、2つの異なる列挙型に同じ列挙型変数があり、それらを解決できることです(これは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??)
enum { COLOR1_RED, COLOR1_GREE, COLOR1_BLUE }
名前空間の問題を簡単に取り除くことができます。名前空間の引数は、ここで言及した3つのうち、私がまったく購入しないものの1つです。
enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }
はEnumクラス:に相当しますenum class Color1 { RED, GREEN, BLUE }
。:アクセスは似ているCOLOR1_RED
対Color1::RED
が、列挙型のバージョンを使用すると、列挙型クラスのことを回避するの名前空間行動タイプミス、のためのより多くの部屋を与え、各値に「COLOR1」と入力が必要です。
列挙は、整数値のセットを表すために使用されます。
の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
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;
}
従来の列挙型は、列挙子を周囲のスコープにエクスポートするため、名前の衝突が発生します。
// 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
};
enumの基になるタイプを指定できないため、混乱、互換性の問題が発生し、前方宣言が不可能になります。
// 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;
}
これらの他の回答に加えて、C ++ 20がenum class
持つ問題の1つである冗長性を解決することは注目に値します。架空の想像enum class
、Color
。
void foo(Color c)
switch (c) {
case Color::Red: ...;
case Color::Green: ...;
case Color::Blue: ...;
// etc
}
}
これはenum
、名前がグローバルスコープ内にあり、プレフィックスを付ける必要がない単純なバリエーションと比べて冗長Color::
です。
ただし、C ++ 20ではusing enum
、列挙型のすべての名前を現在のスコープに導入して問題を解決するために使用できます。
void foo(Color c)
using enum Color;
switch (c) {
case Red: ...;
case Green: ...;
case Blue: ...;
// etc
}
}
だから今、使用しない理由はありませんenum class
。
明示的に言及されていないことの1つ-スコープ機能により、列挙型とクラスメソッドに同じ名前を付けることができます。例えば:
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
};