J'ai entendu quelques personnes recommander d'utiliser des classes enum en C ++ en raison de la sécurité de leur type .
Mais qu'est-ce que cela signifie réellement?
J'ai entendu quelques personnes recommander d'utiliser des classes enum en C ++ en raison de la sécurité de leur type .
Mais qu'est-ce que cela signifie réellement?
Réponses:
C ++ a deux types de enum
:
enum class
esenum
sVoici 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 d'énumérateur sont locaux à l'énumération et leurs valeurs ne sont pas implicitement converties en d'autres types (comme un autre enum
ou int
)
Plain enum
s - où les noms d'énumérateur ont la même portée que l'énumération et leurs valeurs sont implicitement converties 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
es doivent être privilégiées car elles provoquent moins de surprises pouvant potentiellement conduire à des bugs.
A
avec état et que je crée un en enum class State { online, offline };
tant qu'enfant de classe A
, j'aimerais faire des state == online
vérifications à l'intérieur de A
au lieu de state == State::online
... est-ce possible?
enum class
était de l'éliminer.
Color color = Color::red
.
if (color == Card::red_card)
ligne, 4 lignes plus tard que le commentaire (que je vois maintenant s'applique à la première moitié du bloc.) 2 lignes du bloc donnent les mauvais exemples. Les 3 premières lignes ne sont pas un problème. Le «bloc entier est la raison pour laquelle les énumérations simples sont mauvaises» m'a jeté alors que je pensais que vous vouliez dire que quelque chose n'allait pas avec celles-ci aussi. Je vois maintenant, c'est juste une mise en place. Dans tous les cas, merci pour les commentaires.
De la FAQ C ++ 11 de Bjarne Stroustrup :
Les
enum class
es ("nouvelles énumérations", "énumérations fortes") résolvent trois problèmes avec les énumérations C ++ traditionnelles:
- les énumérations conventionnelles sont implicitement converties 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 dans la portée environnante, provoquant des conflits de noms.
- le type sous-jacent d'un
enum
ne peut pas être spécifié, ce qui provoque de la confusion, des problèmes de compatibilité et rend impossible la déclaration anticipée.Les nouvelles énumérations sont «enum class» car elles combinent des aspects d'énumérations traditionnelles (valeurs de noms) avec des aspects de classes (membres de portée 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'un "classique" enum
doit être un type entier suffisamment grand pour s'adapter à toutes les valeurs de enum
; c'est généralement unint
. De plus, chaque type énuméré doit être compatible avec char
ou un type entier signé / non signé.
Il s'agit d'une description large de ce que enum
doit être un type sous-jacent, de sorte que chaque compilateur décidera seul du type sous-jacent du classiqueenum
et parfois le résultat pourrait être surprenant.
Par exemple, j'ai vu du code comme celui-ci plusieurs 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 E_MY_FAVOURITE_FRUITS
valeurs dans un type 8 bits non signé ... mais il n'y a aucune garantie: le compilateur peut choisir unsigned char
ou int
ou short
, n'importe lequel de ces types est assez grand pour s'adapter à tous 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 dépend de la taille du type et / ou suppose qu'il E_MY_FAVOURITE_FRUITS
serait d'une certaine largeur (par exemple: routines de sérialisation), ce code pourrait se comporter de manière étrange selon les 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 de la enum
(en supposant que le compilateur utilisait le plus petit type possible, ce qui est une hypothèse que nous ne pouvons pas faire). Cet ajout simple et imprudent au enum
code pourrait briser la subtilité.
Depuis 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
};
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 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 à la simple énumération? , si nous pouvons choisir le type sous-jacent pour les énumérations scoped ( enum class
) et unscoped ( enum
), quoi d'autre fait 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 d'énumération pour 2 énumérations différentes et que vous pouvez toujours les résoudre (ce qui a été mentionné comme de type sécurisé par OP).
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 s'il 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; //Compile time error(which red are you refering to??)
enum { COLOR1_RED, COLOR1_GREE, COLOR1_BLUE }
, évitant facilement les problèmes d'espace de noms. L'argument d'espace de noms est l'un des trois mentionnés ici que je n'achète pas du tout.
enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }
est comparable à la classe Enum: enum class Color1 { RED, GREEN, BLUE }
. L'accès est similaire: COLOR1_RED
vs Color1::RED
, mais la version Enum nécessite que vous tapiez "COLOR1" dans chaque valeur, ce qui donne plus de place aux fautes de frappe, ce que le comportement d'espace de noms d'une classe enum évite.
enum Color1
, qu'un compilateur ne peut pas attraper car ce serait probablement encore un nom «valide». Si j'écris RED
, GREEN
et ainsi de suite en utilisant une classe enum, cela ne peut pas être résolu enum Banana
car il nécessite que vous le spécifiiez Color1::RED
pour accéder à la valeur (l'argument d'espace de noms). Il y a encore de bons moments à utiliser enum
, mais le comportement d'espace de noms d'un enum class
peut souvent être très bénéfique.
Les énumérations sont utilisées pour représenter un ensemble de valeurs entières.
Le class
mot-clé après le enum
spécifie que l'énumération est fortement typée et ses énumérateurs sont limités. De cette façon, les enum
classes 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 domestiques.
Animal a = Dog; // Error: which DOG?
Animal a = Pets::Dog // Pets::Dog is not an Animal
La FAQ C ++ 11 mentionne les points ci-dessous:
les énumérations conventionnelles sont implicitement converties 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 énumérateurs dans 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 rend la 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;
}
Il convient de noter, en plus de ces autres réponses, que C ++ 20 résout l'un des problèmes qui enum class
a: la verbosité. Imaginer un hypothétique enum class
, Color
.
void foo(Color c)
switch (c) {
case Color::Red: ...;
case Color::Green: ...;
case Color::Blue: ...;
// etc
}
}
Ceci est détaillé par rapport à la enum
variante simple , où les noms sont dans la portée globale et n'ont donc pas besoin d'être préfixés avecColor::
.
Cependant, en C ++ 20, nous pouvons utiliser using enum
pour introduire tous les noms d'une énumération dans la portée actuelle, en résolvant le problème.
void foo(Color c)
using enum Color;
switch (c) {
case Red: ...;
case Green: ...;
case Blue: ...;
// etc
}
}
Alors maintenant, il n'y a aucune raison de ne pas l'utiliser enum class
.
Parce que, comme indiqué dans d'autres réponses, les énumérations de classe ne sont pas implicitement convertibles en int / bool, cela permet également d'éviter le code bogué comme:
enum MyEnum {
Value1,
Value2,
};
...
if (var == Value1 || Value2) // Should be "var == Value2" no error/warning
Une chose qui n'a pas été explicitement mentionnée - la fonctionnalité de portée vous donne la possibilité d'avoir le même nom pour une méthode enum et class. Par exemple:
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
};