web-dev-qa-db-ja.com

なぜenumクラスがplain enumよりも好まれるのですか?

型安全性のために、C++でenum classes を使用することを推奨する人もいます。

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

336
Oleksiy

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

  1. enum classes
  2. 普通のenums

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

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

両者の違いは何ですか?

  • enum classes - 列挙子名は local に列挙型であり、それらの値はnot暗黙のうちに(他の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は、バグにつながる可能性がある驚きの数が少ないため、推奨されます。

360
Oleksiy

からBjarne StroustrupのC++ 11 FAQ

enum classes( "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に変換することはありません。
  • それらは周囲の名前空間を汚染しません。
  • それらは前方宣言することができます。
201
PaperBirdMaster

通常の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 };

基本的な列挙型に関しては、コンパイラは、以下のステートメントのようにredColor1またはColor2型を参照しているかどうかを区別できません。

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

列挙型は一連の整数値を表すために使用されます。

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
19
Alok151290

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

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

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;
}
2
Swapnil
  1. 暗黙的にint型に変換しない
  2. 基礎となるタイプを選択できます
  3. 汚染を防ぐためのENUM名前空間
  4. 通常のクラスと比較して、前方に宣言することができますが、メソッドを持っていません
0
Elon Zhang

明示的に言及されていないことの一つ - スコープ機能はあなたに列挙型とクラスメソッドのために同じ名前を持つためのオプションを与えます。例えば:

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
};
0
Miro Kropacek