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
:
enum class
es 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; }
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 ?:
int
. 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; }