Quand quelqu'un prétend que certaines constructions de programmation sont "mauvaises", il essaie de vous décourager de penser par vous-même.
Pete Becker
3
@NicolBolas: Il s'agit plus d'une question réthorique pour fournir une réponse à la FAQ (que ce soit vraiment Frequenty demandé, c'est une autre histoire).
David Rodríguez - dribeas
@David, il y a une discussion pour savoir si cela devrait être une FAQ ou non qui commence ici . Entrée bienvenue.
sbi
17
@PeteBecker Parfois, ils essaient simplement de vous protéger de vous-même.
enumclassColor{ red, green, blue };// enum classenumAnimal{ dog, cat, bird, human };// plain enum
Quelle est la différence entre deux?
enum classes - 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 enumou int)
Plain enums - 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:
enumColor{ red, green, blue };// plain enum enumCard{ red_card, green_card, yellow_card };// another plain enum enumclassAnimal{ dog, deer, cat, bird, human };// enum classenumclassMammal{ kangaroo, deer, human };// another enum classvoid fun(){// examples of bad use of plain enums:Color color =Color::red;Card card =Card::green_card;int num = color;// no problemif(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;// errorif(m == a)// error (good)
cout <<"bad"<< endl;if(a ==Mammal::deer)// error (good)
cout <<"bad"<< endl;}
Conclusion:
enum classes doivent être privilégiées car elles provoquent moins de surprises pouvant potentiellement conduire à des bugs.
Bon exemple ... existe-t-il un moyen de combiner la sécurité de type de la version de classe avec la promotion d'espace de noms de la version enum? Autrement dit, si j'ai une classe Aavec état et que je crée un en enum class State { online, offline };tant qu'enfant de classe A, j'aimerais faire des state == onlinevérifications à l'intérieur de Aau lieu de state == State::online... est-ce possible?
marquez
31
Nan. La promotion de l'espace de noms est une mauvaise chose ™ et la moitié de la justification enum classétait de l'éliminer.
Chiot
10
En C ++ 11, vous pouvez également utiliser des énumérations typées explicitement, comme enum Animal: unsigned int {chien, cerf, chat, oiseau}
Blasius Secundus
3
@Cat Plus Plus Je comprends que @Oleksiy dit que c'est mauvais. Ma question n'était pas de savoir si Oleksiy pensait que c'était mauvais. Ma question était une demande de détailler ce qui est mauvais à ce sujet. Plus précisément, pourquoi Oleksiy, par exemple, considère-t-il mauvais Color color = Color::red.
chux
9
@Cat Plus Plus Donc, le mauvais exemple ne se produit pas avant la 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.
Les enum classes ("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 enumne 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" enumdoit ê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 charou un type entier signé / non signé.
Il s'agit d'une description large de ce que enumdoit ê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_FRUITSvaleurs dans un type 8 bits non signé ... mais il n'y a aucune garantie: le compilateur peut choisir unsigned charou intou 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_FORCE8est 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_FRUITSserait 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 enumcode pourrait briser la subtilité.
Depuis C ++ 11 est possible de spécifier le type sous-jacent pour enumet enum class(merci rdb ) donc ce problème est soigneusement résolu:
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 classun meilleur choix?:
Je suppose que nous pouvons également restreindre le type de base enum pour les énumérations régulières, tant que nous avons C ++ 11
Sagar Padhye
11
Désolé, mais cette réponse est fausse. "enum class" n'a rien à voir avec la possibilité de spécifier le type. C'est une fonctionnalité indépendante qui existe à la fois pour les énumérations régulières et pour les classes d'énumérations.
rdb
14
Voici l'affaire: * Les classes Enum sont une nouvelle fonctionnalité de C ++ 11. * Les énumérations typées sont une nouvelle fonctionnalité de C ++ 11. Ce sont deux nouvelles fonctionnalités distinctes non liées dans C ++ 11. Vous pouvez utiliser les deux, ou vous pouvez utiliser l'un ou l'autre.
rdb
2
Je pense qu'Alex Allain fournit l' explication simple la plus complète que j'ai jamais vue dans ce blog à [ cprogramming.com/c++11/… . L' énumération traditionnelle était bonne pour utiliser des noms au lieu de valeurs entières et éviter d'utiliser le préprocesseur #defines, ce qui était une bonne chose - cela a ajouté de la clarté. La classe enum supprime le concept d'une valeur numérique de l'énumérateur, et introduit la portée et le typage fort qui augmente (enfin, peut augmenter :-) l'exactitude du programme. Il vous rapproche d'un pas de la pensée orientée objet.
Jon Spencer
2
En passant, c'est toujours amusant quand vous passez en revue le code et soudainement One Piece se produit.
Justin Time - Rétablir Monica le
47
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:
enumclassColor1{ red, green, blue };//this will compileenumclassColor2{ red, green, blue };enumColor1{ red, green, blue };//this will not compile enumColor2{ red, green, blue };
En ce qui concerne les énumérations de base, le compilateur ne pourra pas distinguer s'il redfait référence au type Color1ou Color2comme dans l'instruction ci-dessous.
enumColor1{ red, green, blue };enumColor2{ red, green, blue };int x = red;//Compile time error(which red are you refering to??)
@Oleksiy Ohh, je n'ai pas bien lu votre question. Considérez-le comme un complément pour ceux qui ne le savaient pas.
Saksham
c'est bon! J'ai presque oublié ça
Oleksiy
bien sûr, vous écririez 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.
Jo So
2
@Jo So Cette solution est une solution de contournement inutile. Enum: 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_REDvs 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.
cdgraham
2
Veuillez utiliser une critique constructive . Quand je dis plus de place pour les fautes de frappe, je veux dire quand vous définissez à l'origine les valeurs de enum Color1, qu'un compilateur ne peut pas attraper car ce serait probablement encore un nom «valide». Si j'écris RED, GREENet ainsi de suite en utilisant une classe enum, cela ne peut pas être résolu enum Bananacar il nécessite que vous le spécifiiez Color1::REDpour 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 classpeut souvent être très bénéfique.
cdgraham
20
Les énumérations sont utilisées pour représenter un ensemble de valeurs entières.
Le classmot-clé après le enumspécifie que l'énumération est fortement typée et ses énumérateurs sont limités. De cette façon, les enumclasses empêchent une mauvaise utilisation accidentelle des constantes.
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};enumclassNewColor{Red_1,Green_1,Yellow_1};int main(){//! Implicit conversion is possibleint i =Red;//! Need enum class name followed by access specifier. Ex: NewColor::Red_1int 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'return0;}
les énumérations conventionnelles exportent leurs énumérateurs dans la portée environnante, provoquant des conflits de noms.
// Header.henum vehicle
{Car,Bus,Bike,Autorickshow};enumFourWheeler{Car,// error C2365: 'Car': redefinition; previous definition was 'enumerator'SmallBus};enumclassEditor{
vim,
eclipes,VisualStudio};enumclassCppEditor{
eclipes,// No error of redefinitionsVisualStudio,// No error of redefinitionsQtCreator};
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.
C ++ 11 permet énumérations « non-classe » à tapées ainsi. Les problèmes de pollution des espaces de noms, etc., existent toujours. Jetez un œil aux réponses pertinentes qui existaient bien avant celle-ci.
user2864740
7
ne pas convertir implicitement en int
peut choisir le type sous-jacent
Espace de noms ENUM pour éviter la pollution
Comparé à la classe normale, peut être déclaré en avant, mais n'a pas de méthode
Il convient de noter, en plus de ces autres réponses, que C ++ 20 résout l'un des problèmes qui enum classa: la verbosité. Imaginer un hypothétique enum class, Color.
Ceci est détaillé par rapport à la enumvariante 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 enumpour introduire tous les noms d'une énumération dans la portée actuelle, en résolvant le problème.
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:
enumMyEnum{Value1,Value2,};...if(var ==Value1||Value2)// Should be "var == Value2" no error/warning
Pour terminer mon commentaire précédent, notez que gcc a maintenant un avertissement appelé -Wint-in-bool-context qui détectera exactement ce type d'erreurs.
Arnaud
0
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:
classTest{public:// these call ProcessCommand() internallyvoidTakeSnapshot();voidRestoreSnapshot();private:enumclassCommand// wouldn't be possible without 'class'{TakeSnapshot,RestoreSnapshot};voidProcessCommand(Command cmd);// signal the other thread or whatever};
enum
vsenum class
.Réponses:
C ++ a deux types de
enum
:enum class
esenum
sVoici quelques exemples pour les déclarer:
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 autreenum
ouint
)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 typesExemple:
Conclusion:
enum class
es doivent être privilégiées car elles provoquent moins de surprises pouvant potentiellement conduire à des bugs.la source
A
avec état et que je crée un enenum class State { online, offline };
tant qu'enfant de classeA
, j'aimerais faire desstate == online
vérifications à l'intérieur deA
au lieu destate == 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 :
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 deenum
; c'est généralement unint
. De plus, chaque type énuméré doit être compatible avecchar
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:
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 choisirunsigned char
ouint
oushort
, n'importe lequel de ces types est assez grand pour s'adapter à tous les valeurs vues dans leenum
. L'ajout du champE_MY_FAVOURITE_FRUITS_FORCE8
est un fardeau et n'oblige pas le compilateur à faire un choix quelconque sur le type sous-jacent duenum
.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
: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 auenum
code pourrait briser la subtilité.Depuis C ++ 11 est possible de spécifier le type sous-jacent pour
enum
etenum class
(merci rdb ) donc ce problème est soigneusement résolu: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 faitenum class
un meilleur choix?:int
.la source
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:
En ce qui concerne les énumérations de base, le compilateur ne pourra pas distinguer s'il
red
fait référence au typeColor1
ouColor2
comme dans l'instruction ci-dessous.la source
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
vsColor1::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'écrisRED
,GREEN
et ainsi de suite en utilisant une classe enum, cela ne peut pas être résoluenum Banana
car il nécessite que vous le spécifiiezColor1::RED
pour accéder à la valeur (l'argument d'espace de noms). Il y a encore de bons moments à utiliserenum
, mais le comportement d'espace de noms d'unenum 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 leenum
spécifie que l'énumération est fortement typée et ses énumérateurs sont limités. De cette façon, lesenum
classes empêchent une mauvaise utilisation accidentelle des constantes.Par exemple:
Ici, nous ne pouvons pas mélanger les valeurs des animaux et des animaux domestiques.
la source
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.
les énumérations conventionnelles exportent leurs énumérateurs dans la portée environnante, provoquant des conflits de noms.
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.
.
.
la source
la source
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étiqueenum class
,Color
.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.Alors maintenant, il n'y a aucune raison de ne pas l'utiliser
enum class
.la source
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:
la source
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:
la source