Pourquoi la classe enum est-elle préférée à la énumération standard?

J’ai entendu quelques personnes recommander d’utiliser des classes enum en C ++ en raison de leur type de sécurité .

Mais qu’est-ce que cela signifie réellement?

C ++ a deux sortes d’ enum :

  1. enum class es
  2. enum simple

Voici quelques exemples pour les déclarer:

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

Quelle est la différence entre deux?

  • enum class es – les noms des énumérateurs sont locaux à l’énumération et leurs valeurs ne sont pas implicitement converties en d’autres types (comme un autre enum ou int )

  • enum simple s – où les noms des énumérateurs ont la même scope que l’énumération et que leurs valeurs convertissent implicitement en entiers et autres types

Exemple:

 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; } 

Conclusion:

enum class devraient être préférées car elles provoquent moins de sursockets pouvant potentiellement conduire à des bogues.

Extrait de la FAQ C ++ 11 de Bjarne Stroustrup :

Les enum class es (“new enums”, ” enum class fortes”) résolvent trois problèmes avec les énumérations C ++ traditionnelles:

  • les énumérations conventionnelles convertissent implicitement en int, provoquant des erreurs lorsque quelqu’un ne veut pas qu’une énumération agisse comme un entier.
  • les énumérations classiques exportent leurs enquêteurs vers la scope environnante, provoquant des conflits de noms.
  • le type sous-jacent d’un enum ne peut pas être spécifié, ce qui entraîne une confusion, des problèmes de compatibilité et rend la déclaration en avant impossible.

Les nouvelles énumérations sont “enum class” car elles combinent des aspects des énumérations traditionnelles (valeurs de noms) avec des aspects de classes (membres de scope et absence de conversions).

Ainsi, comme mentionné par d’autres utilisateurs, les “énumérations fortes” rendraient le code plus sûr.

Le type sous-jacent d’une enum “classique” doit être un type entier suffisamment grand pour correspondre à toutes les valeurs de l’ enum ; c’est généralement un int . De plus, chaque type énuméré doit être compatible avec char ou un type d’entier signé / non signé.

Ceci est une description large de ce qu’un type sous-jacent doit être, de sorte que chaque compilateur prendra lui-même des décisions sur le type sous-jacent de l’ enum classique et parfois le résultat pourrait être surprenant.

Par exemple, j’ai vu du code comme ça un tas de fois:

 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? }; 

Dans le code ci-dessus, un codeur naïf pense que le compilateur stockera les valeurs E_MY_FAVOURITE_FRUITS dans un type 8bit non signé … mais il n’y a aucune garantie à ce sujet: le compilateur peut choisir un caractère unsigned char ou int ou short . assez pour correspondre à toutes les valeurs vues dans l’ enum . L’ajout du champ E_MY_FAVOURITE_FRUITS_FORCE8 est un fardeau et n’oblige pas le compilateur à faire un choix quelconque sur le type sous-jacent de l’ enum .

S’il existe un morceau de code qui dépend de la taille du type et / ou suppose que E_MY_FAVOURITE_FRUITS aurait une certaine largeur (par exemple: routines de sérialisation), ce code pourrait se comporter de manière étrange en fonction des pensées du compilateur.

Et pour aggraver les choses, si un collègue ajoute une nouvelle valeur à notre enum :

  E_DEVIL_FRUIT = 0x100, // New fruit, with value greater than 8bits 

Le compilateur ne s’en plaint pas! Il redimensionne simplement le type pour l’adapter à toutes les valeurs de l’ enum (en supposant que le compilateur utilisait le plus petit type possible, ce qui est une hypothèse impossible). Cet ajout simple et négligent à l’ enum pourrait subtiliser le code associé.

Comme C ++ 11 est possible de spécifier le type sous-jacent pour enum et enum class (merci rdb ), ce problème est clairement résolu:

 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 }; 

En spécifiant le type sous-jacent si un champ a une expression hors de la plage de ce type, le compilateur se plaindra au lieu de modifier le type sous-jacent.

Je pense que c’est une bonne amélioration de la sécurité.

Alors, pourquoi la classe enum est-elle préférée à la simple énumération? , si nous pouvons choisir le type sous-jacent pour les enum class de scope ( enum class ) et non-énumérées ( enum ), quoi d’autre rend la enum class un meilleur choix ?:

  • Ils ne convertissent pas implicitement en int .
  • Ils ne polluent pas le namespace environnant.
  • Ils peuvent être déclarés à l’avance.

L’avantage de base de l’utilisation de la classe enum par rapport aux énumérations normales est que vous pouvez avoir les mêmes variables enum pour 2 énumérations différentes et toujours pouvoir les résoudre (ce qui a été mentionné comme étant de type sécurisé par OP)

Pour par exemple:

 enum class Color1 { red, green, blue }; //this will comstack enum class Color2 { red, green, blue }; enum Color1 { red, green, blue }; //this will not comstack enum Color2 { red, green, blue }; 

En ce qui concerne les énumérations de base, le compilateur ne sera pas en mesure de distinguer si le red fait référence au type Color1 ou Color2 comme dans l’instruction ci-dessous.

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

Les énumérations sont utilisées pour représenter un ensemble de valeurs entières.

Le mot class clé class après l’ enum spécifie que l’énumération est fortement typée et que ses énumérateurs sont définis. De cette façon, les classes enum empêchent une mauvaise utilisation accidentelle des constantes.

Par exemple:

 enum class Animal{Dog, Cat, Tiger}; enum class Pets{Dog, Parrot}; 

Ici, nous ne pouvons pas mélanger les valeurs Animal et Pets.

 Animal a = Dog; // Error: which DOG? Animal a = Pets::Dog // Pets::Dog is not an Animal 

C ++ 11 FAQ mentionne les points ci-dessous:

les énumérations conventionnelles convertissent implicitement en int, provoquant des erreurs lorsque quelqu’un ne veut pas qu’une énumération agisse comme un entier.

 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; } 

les énumérations classiques exportent leurs enquêteurs vers la scope environnante, provoquant des conflits de noms.

 // 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 }; 

Le type sous-jacent d’une énumération ne peut pas être spécifié, entraînant une confusion, des problèmes de compatibilité et rend la déclaration en avant impossible.

 // Header1.h #include  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; }