Pourquoi la classe enum est-elle préférée à l'enum simple?
J'ai entendu quelques personnes recommander d'utiliser enum classes en C++ en raison de leur sécurité de type .
Mais qu'est-ce que cela signifie vraiment?
5 réponses
C++ a deux types de enum
:
-
enum class
es - Plaine
enum
s
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 les deux?
enum class
les noms es-enumerator sont local à l'énumération et leurs valeurs nepas implicitement convertir en d'autres types (comme un autreenum
ouint
)-
Plain
enum
S-où les noms d'énumérateur sont dans le même portée comme l'énumération et leur les 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
es devrait être préféré car ils causent moins de surprises qui pourraient potentiellement conduire à des bugs.
De FAQ C++11 de Bjarne Stroustrup :
Les
enum class
es ("New enums"," strong enums") abordent trois problèmes avec les énumérations c++ traditionnelles:
- les énumérations conventionnelles se convertissent implicitement en int, provoquant des erreurs lorsque quelqu'un ne veut pas qu'une énumération agisse comme un entier.
- les énumérations conventionnelles exportent leurs énumérateurs vers la portée environnante, provoquant des conflits de noms.
- le type sous-jacent d'un
enum
ne peut pas être spécifié, causant la confusion, les problèmes de compatibilité, et fait la déclaration en avant impossible.Les nouvelles énumérations sont "Classe enum" car elles combinent des aspects des énumérations traditionnelles (valeurs de noms) avec des aspects des classes (membres de portée et absence de conversions).
Ainsi, comme mentionné par d'autres utilisateurs, les "énums forts" rendraient le code plus sûr.
Le type sous-jacent d'un" classique " enum
doit être un type entier suffisamment grand pour valeurs du enum
; c'est généralement un int
. En outre, chaque type énuméré doit être compatible avec char
ou un type entier signé/non signé.
Ceci est une large description de ce que doit être un type sous-jacent enum
, de sorte que chaque compilateur prendra lui-même des décisions sur le type sous-jacent du Classique enum
et parfois le résultat pourrait être surprenant.
Par exemple, j'ai vu du code comme celui - ci 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 8 bits non signé... mais il n'y a aucune garantie à ce sujet: le compilateur peut choisir unsigned char
ou int
ou short
, l'un de ces types est assez grand pour s'adapter à toutes les valeurs vues dans le 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 du enum
.
S'il y a un morceau de code qui repose sur la taille du type et / ou suppose que E_MY_FAVOURITE_FRUITS
serait d'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 négligemment 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 s'adapter à toutes les valeurs du enum
(en supposant que le compilateur utilisait le plus petit type possible,ce qui est une hypothèse que nous ne pouvons pas faire). Cette addition simple et négligente au enum
pourrait subtilité briser le code lié.
Puisque C++11 est possible de spécifier le type sous-jacent pour enum
et enum class
(Merci rdb ) donc ce problème est soigneusement 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
};
Spécification du type sous-jacent si un champ a une expression hors de la plage de ce type, le compilateur se plaindra au lieu de changer 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 à l'enum simple?, si nous pouvons choisir le type sous-jacent pour les énumérations scoped(enum class
) et unscoped (enum
) quoi d'autre fait de enum class
un meilleur choix?:
- ils ne se convertissent pas implicitement en
int
. - ils ne polluent pas l'espace de noms environnant.
- ils peuvent être déclarés en avant.
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 que vous pouvez toujours les résoudre (ce qui a été mentionné comme type safe par OP)
Pour par exemple:
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 };
En ce qui concerne les énumérations de base, le compilateur ne pourra pas distinguer si red
fait référence au type Color1
ou Color2
comme dans l'instruction hte ci-dessous.
enum Color1 { red, green, blue };
enum Color2 { red, green, blue };
int x = red; //Compile 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 clé class
après le enum
spécifie que l'énumération est fortement typée et que ses énumérateurs sont étendus. 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 des animaux et des animaux de compagnie.
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 se 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 conventionnelles exportent leurs recenseurs vers la portée 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é, ce qui provoque de la confusion, des problèmes de compatibilité et fait une déclaration directe impossible.
// 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;
}