なぜenumクラスはプレーンなenumよりも優先されるのですか?


429

型の安全性のために、C ++で列挙型クラスを使用することを勧める人が何人かいます

しかし、それは本当にどういう意味ですか?


57
誰かがプログラミング構造が「悪」だと主張するとき、彼らはあなたが自分で考えることを思いとどまらせようとしています。
ピートベッカー

3
@NicolBolas:これは、FAQの回答を提供するためのより再考的な質問です(これが本当によくある質問かどうかは別の話です)。
デビッドロドリゲス-ドリベス2013

@David、これがFAQになるべきかどうか、ここから始まる議論はありません。ようこそ。
sbi 2013

17
@PeteBecker時々彼らは単にあなたをあなた自身から守ろうとしているだけです。
-piccy

geeksforgeeks.org/…これは、enumvs を理解するのにも良い場所enum classです。
mr_azad

回答:


472

C ++には次の2種類がありenumます。

  1. enum classes
  2. プレーンenums

これらを宣言する方法の例をいくつか示します。

 enum class Color { red, green, blue }; // enum class
 enum Animal { dog, cat, bird, human }; // plain enum 

2つの違いは何ですか?

  • enum classes-列挙子の名前はenum に対してローカルであり、その値は暗黙的に他の型(別のorのような)に変換されませenumint

  • プレーンenums-列挙子の名前は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 classesは、バグの原因となる可能性のある驚きが少ないため、推奨されます。


7
良い例...クラスバージョンの型の安全性を列挙型バージョンの名前空間の昇格と組み合わせる方法はありますか?つまり、Astate を持つクラスがあり、class enum class State { online, offline };の子としてを作成した場合、... ではなく内部でチェックAを実行したいのですが、それは可能ですか?state == onlineAstate == State::online
マーク

31
いいえ。名前空間の昇格はBad Thing™であり、正当化する理由の半分はenum classそれを排除することでした。
子犬

10
C ++ 11では、enum Animal:unsigned int {dog、deer、cat、bird}のように明示的に型指定されたenumも使用できます
Blasius Secundus

3
@Cat Plus Plus @Oleksiyが悪いと言っていることを理解しています。私の質問は、オレクシーがそれが悪いと思っているかどうかではありませんでした。私の質問はそれについてが悪いの詳細を尋ねる質問でした。具体的には、たとえばOleksiyがなぜについて悪いと考えるのかColor color = Color::red
chux-モニカを2013年

9
プラスプラスだから、@Cat例の悪いが発生しなくなるまでif (color == Card::red_card)行、4行後のコメントよりも(私が今見るブロックの最初の半分に適用されます。)ブロックの2行が与え悪い例を。最初の3行は問題ではありません。「ブロック全体がプレーンな列挙型が悪い理由です」と私は思いました。私は今、それは単なるセットアップです。いずれにしても、フィードバックをありがとう。
chux-2013年

248

ビャーネ・ストロヴストルップのC ++ 11よくある質問

enum classES(「新しい列挙型」、「強い列挙型」)アドレス伝統的な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ではenumand の基になる型を指定できるためenum classrdbに感謝)、この問題は適切に対処されます。

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
  • それらは周囲の名前空間を汚染しません。
  • それらは前方宣言することができます。

1
C ++ 11がある限り、通常の列挙型でも列挙型の基本型を制限できると思います
Sagar Padhye

11
申し訳ありませんが、この答えは間違っています。「列挙型クラス」は、タイプを指定する機能とは関係ありません。これは、通常の列挙型と列挙型クラスの両方に存在する独立した機能です。
rdb 2016年

14
これは取り決めです:* EnumクラスはC ++ 11の新機能です。*型付き列挙型はC ++ 11の新機能です。これらは、C ++ 11の2つの別個の無関係な新機能です。両方を使用することも、どちらか一方を使用することも、どちらも使用しないこともできます。
rdb 2016年

2
Alex Allainが最も完全なものを提供すると思います このブログ[ cprogramming.com/c++11/…]でまだ見たことのない簡単な説明を。従来の列挙型は、整数値の代わりに名前を使用し、プリプロセッサ#definesの使用を回避するのに適していました。 enumクラスは、列挙子の数値の概念を取り除き、プログラムの正確さを向上させる(まあ、:-) ことができるスコープと強い型付けを導入します。オブジェクト指向の考え方に一歩近づきます。
Jon Spencer

2
余談ですが、コードをレビューしているときにいつもおもしろくて、突然ワンピースが起こります。
ジャスティンタイム-モニカを復活させる

47

通常の列挙型よりも列挙型クラスを使用する場合の基本的な利点は、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型を参照しているColor1Color2、以下のステートメントのように参照しているかを区別できません。

enum Color1 { red, green, blue };   
enum Color2 { red, green, blue };
int x = red;    //Compile time error(which red are you refering to??)

1
@Oleksiyおお、私はあなたの質問を正しく読みませんでした。知らない人のためのアドオンとして検討してください。
Saksham 2013

大丈夫です!私はこれについてほとんど忘れていました
Oleksiy

もちろん、enum { COLOR1_RED, COLOR1_GREE, COLOR1_BLUE }名前空間の問題を簡単に取り除くことができます。名前空間の引数は、ここで言及した3つのうち、私がまったく購入しないものの1つです。
Jo So

2
@Jo So Soソリューションは不必要な回避策です。Enum:enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }はEnumクラス:に相当しますenum class Color1 { RED, GREEN, BLUE }。:アクセスは似ているCOLOR1_REDColor1::REDが、列挙型のバージョンを使用すると、列挙型クラスのことを回避するの名前空間行動タイプミス、のためのより多くの部屋を与え、各値に「COLOR1」と入力が必要です。
cdgraham

2
建設的な批評を使用してください。タイプミスの余地があるとは、最初にの値を定義したときを意味しますenum Color1。これは、まだ「有効な」名前である可能性が高いため、コンパイラがキャッチできない値です。私が書く場合REDGREENおよびのでそれが解決できないよりも、列挙型クラスを使用してenum Banana、それはあなたが指定する必要があるため、Color1::RED値(名前空間の引数)にアクセスするために。を使用するのenumに良い時期はまだありますが、enum class多くの場合、の名前空間の動作は非常に有益です。
cdgraham

20

列挙は、整数値のセットを表すために使用されます。

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

7

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 ++ 11では、「非クラス」の列挙型も型指定できます。名前空間の汚染問題などがまだ残っています。...この1の前に長い時間が存在し、関連の回答を見てみましょう
user2864740

7
  1. 暗黙的にintに変換しません
  2. 根底にあるタイプを選択できます
  3. 汚染を回避するためのENUM名前空間
  4. 通常のクラスと比較して、前方に宣言できますが、メソッドはありません

2

これらの他の回答に加えて、C ++ 20がenum class持つ問題の1つである冗長性を解決することは注目に値します。架空の想像enum classColor

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

他の回答で述べたように、クラスenumは暗黙的にint / boolに変換できないため、次のようなバグのあるコードを回避するのにも役立ちます。

enum MyEnum {
  Value1,
  Value2,
};
...
if (var == Value1 || Value2) // Should be "var == Value2" no error/warning

2
以前のコメントを完了するために、gccには-Wint-in-bool-contextと呼ばれる警告があり、この種のエラーを正確にキャッチすることに注意してください。
Arnaud

0

明示的に言及されていないことの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
};
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.