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?

272
demandé sur Community 2013-08-20 17:06:13

5 réponses

C++ a deux types de enum:

  1. enum classes
  2. Plaine enums

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 classles noms es-enumerator sont local à l'énumération et leurs valeurs nepas implicitement convertir en d'autres types (comme un autre enum ou int)

  • Plain enumS-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 classes devrait être préféré car ils causent moins de surprises qui pourraient potentiellement conduire à des bugs.

294
répondu Oleksiy 2013-08-20 14:46:30

De FAQ C++11 de Bjarne Stroustrup :

Les enum classes ("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.
177
répondu Paula_plus_plus 2018-08-06 15:58:30

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??)
39
répondu Saksham 2013-08-20 13:18:29

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
18
répondu Alok151290 2015-02-04 05:51:42

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;
}
1
répondu Swapnil 2018-08-02 12:28:11