Perché la classe enum è preferita rispetto alla semplice enum?


429

Ho sentito alcune persone raccomandare di usare le classi enum in C ++ a causa della loro sicurezza del tipo .

Ma cosa significa veramente?


57
Quando qualcuno afferma che un costrutto di programmazione è "malvagio", sta cercando di scoraggiarti dal pensare da solo.
Pete Becker,

3
@NicolBolas: Questa è più una domanda retorica per fornire una risposta alle FAQ (se si tratta davvero di una domanda frequente è una storia diversa).
David Rodríguez - dribeas,

@ David, c'è una discussione se questa dovrebbe essere una FAQ o no che inizia qui . Ingresso benvenuto.
sabato

17
@PeteBecker A volte stanno semplicemente cercando di proteggerti da te stesso.
piccy,

geeksforgeeks.org/... Questo è anche un buon posto per capire enumvs enum class.
mr_azad,

Risposte:


473

C ++ ha due tipi di enum:

  1. enum classes
  2. Plain enums

Ecco un paio di esempi su come dichiararli:

 enum class Color { red, green, blue }; // enum class
 enum Animal { dog, cat, bird, human }; // plain enum 

Qual è la differenza tra due?

  • enum classes - i nomi degli enumeratori sono locali nell'enum e i loro valori non vengono convertiti implicitamente in altri tipi (come un altro enumo int)

  • Plain enums - in cui i nomi degli enumeratori sono nello stesso ambito dell'enum e i loro valori vengono convertiti implicitamente in numeri interi e altri tipi

Esempio:

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;

}

Conclusione:

enum classes dovrebbe essere preferito perché causano meno sorprese che potrebbero potenzialmente portare a bug.


7
Un buon esempio ... c'è un modo per combinare la sicurezza dei tipi della versione di classe con la promozione dello spazio dei nomi della versione di enum? Cioè, se ho una classe Acon stato e ne creo una enum class State { online, offline };come figlia di classe A, mi piacerebbe fare state == onlinecontrolli all'interno Ainvece di state == State::online... è possibile?
segna il

31
No. La promozione dello spazio dei nomi è una cosa negativa e metà della giustificazione è enum classstata eliminarla.
Cucciolo

10
In C ++ 11, puoi usare anche enum esplicitamente digitati, come enum Animal: unsigned int {cane, cervo, gatto, uccello}
Blasius Secundus

3
@Cat Plus Plus Capisco che @Oleksiy dice che è male. La mia domanda non era se Oleksiy pensasse che fosse male. La mia domanda era una richiesta per dettagliare ciò che è male. In particolare, perché Oleksiy, ad esempio, considera male Color color = Color::red.
chux - Ripristina Monica il

9
@Cat Plus Plus Quindi l'esempio negativo non si verifica fino alla if (color == Card::red_card)riga, 4 righe dopo il commento (che vedo ora si applica alla prima metà del blocco.) 2 righe del blocco forniscono gli esempi negativi . Le prime 3 righe non sono un problema. "L'intero blocco è il motivo per cui gli enumeratori semplici sono cattivi" mi ha gettato mentre pensavo che volessi dire che c'era qualcosa di sbagliato anche in quelli. Vedo ora, è solo un set-up. In ogni caso, grazie per il feedback.
chux - Ripristina Monica il

248

Dalle FAQ C ++ 11 di Bjarne Stroustrup :

Gli enum classes ("new enums", "strong enums") affrontano tre problemi con le enumerazioni C ++ tradizionali:

  • Gli enum convenzionali vengono convertiti in modo implicito in int, causando errori quando qualcuno non desidera che un'enumerazione funzioni come un numero intero.
  • gli enum convenzionali esportano i loro enumeratori nell'ambito circostante, causando scontri tra nomi.
  • il tipo sottostante di un enumnon può essere specificato, causando confusione, problemi di compatibilità e rendendo impossibile la dichiarazione anticipata.

I nuovi enum sono "enum class" perché combinano aspetti delle enumerazioni tradizionali (valori dei nomi) con aspetti delle classi (membri con ambito e assenza di conversioni).

Quindi, come menzionato da altri utenti, i "punti forti" renderebbero il codice più sicuro.

Il tipo sottostante di un "classico" enumdeve essere un tipo intero abbastanza grande da contenere tutti i valori di enum; questo di solito è un int. Inoltre, ogni tipo elencato deve essere compatibile con charun tipo intero con o senza segno.

Questa è un'ampia descrizione di ciò enumche deve essere un tipo sottostante, quindi ogni compilatore prenderà le proprie decisioni sul tipo sottostante del classico enume talvolta il risultato potrebbe essere sorprendente.

Ad esempio, ho visto codice come questo un sacco di volte:

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?
};

Nel codice sopra, un programmatore ingenuo sta pensando che il compilatore memorizzerà i E_MY_FAVOURITE_FRUITSvalori in un tipo a 8 bit senza segno ... ma non c'è alcuna garanzia al riguardo: il compilatore può scegliere unsigned charo intoppure short, uno di questi tipi è abbastanza grande da adattarsi a tutti i valori visti in enum. L'aggiunta del campo E_MY_FAVOURITE_FRUITS_FORCE8è un onere e non impone al compilatore di fare alcun tipo di scelta sul tipo sottostante di enum.

Se c'è un pezzo di codice che si basa sulla dimensione del tipo e / o presume che E_MY_FAVOURITE_FRUITSsia di una certa larghezza (es. Routine di serializzazione) questo codice potrebbe comportarsi in qualche modo strano a seconda dei pensieri del compilatore.

E a peggiorare le cose, se un compagno di lavoro aggiunge con noncuranza un nuovo valore al nostro enum:

    E_DEVIL_FRUIT  = 0x100, // New fruit, with value greater than 8bits

Il compilatore non se ne lamenta! Ridimensiona semplicemente il tipo per adattarlo a tutti i valori di enum(supponendo che il compilatore stesse usando il tipo più piccolo possibile, il che è un presupposto che non possiamo fare). Questa aggiunta semplice e spensierata al enumcodice potrebbe spezzare sottilmente.

Poiché C ++ 11 è possibile specificare il tipo sottostante per enume enum class(grazie rdb ), questo problema viene risolto in modo chiaro:

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
};

Specificando il tipo sottostante se un campo ha un'espressione al di fuori dell'intervallo di questo tipo, il compilatore si lamenterà invece di cambiare il tipo sottostante.

Penso che questo sia un buon miglioramento della sicurezza.

Quindi perché la classe enum è preferita rispetto alla semplice enum? , se possiamo scegliere il tipo sottostante per enum classenumerati scoped ( ) e unscoped ( enum) cos'altro fa enum classuna scelta migliore ?:

  • Non si convertono implicitamente in int.
  • Non inquinano lo spazio dei nomi circostante.
  • Possono essere dichiarati in avanti.

1
Suppongo che possiamo limitare il tipo di base enum anche per gli enum regolari, purché abbiamo C ++ 11
Sagar Padhye,

11
Siamo spiacenti, ma questa risposta è sbagliata. "enum class" non ha nulla a che fare con la possibilità di specificare il tipo. Questa è una funzionalità indipendente che esiste sia per gli enum regolari che per le classi di enum.
rdb,

14
Questo è il problema: * Le classi Enum sono una nuova funzionalità in C ++ 11. * Gli enumerativi digitati sono una nuova funzionalità in C ++ 11. Queste sono due nuove funzionalità non correlate separate in C ++ 11. È possibile utilizzare entrambi, oppure è possibile utilizzare uno o nessuno dei due.
rdb,

2
Penso che Alex Allain fornisca la spiegazione più semplice e completa che abbia mai visto in questo blog su [ cprogramming.com/c++11/… . L' enum tradizionale era buono per usare nomi invece di valori interi ed evitare l'uso del preprocessore #define, che era una buona cosa - aggiungeva chiarezza. La classe enum rimuove il concetto di un valore numerico dell'enumeratore e introduce l'ambito e la tipizzazione forte che aumenta (beh, può aumentare la correttezza del programma :-). Ti avvicina di un passo al pensiero orientato agli oggetti.
Jon Spencer,

2
Per inciso, è sempre divertente quando rivedi il codice e all'improvviso succede One Piece .
Justin Time - Ripristina Monica il

47

Il vantaggio di base dell'utilizzo della classe enum rispetto agli enum normali è che si possono avere le stesse variabili enum per 2 enum diversi e tuttavia è ancora possibile risolverli (che è stato citato come tipo sicuro dall'OP)

Ad esempio:

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 };

Per quanto riguarda gli enum di base, il compilatore non sarà in grado di distinguere se redsi riferisce al tipo Color1o Color2come nell'istruzione hte sotto.

enum Color1 { red, green, blue };   
enum Color2 { red, green, blue };
int x = red;    //Compile time error(which red are you refering to??)

1
@Oleksiy Ohh non ho letto correttamente la tua domanda. Considerare è come un componente aggiuntivo per coloro che non lo sapevano.
Saksham,

va bene! Me ne sono quasi dimenticato
Oleksiy,

ovviamente, scriveresti enum { COLOR1_RED, COLOR1_GREE, COLOR1_BLUE }, ovviando facilmente ai problemi dello spazio dei nomi. L'argomento dello spazio dei nomi è uno dei tre citati qui che non compro affatto.
Jo So,

2
@Jo So That soluzione è una soluzione inutile. Enum: enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }è paragonabile alla classe Enum: enum class Color1 { RED, GREEN, BLUE }. L'accesso è simile: COLOR1_REDvs Color1::RED, ma la versione di Enum richiede di digitare "COLOR1" in ciascun valore, il che dà più spazio ai refusi, che il comportamento dello spazio dei nomi di una classe enum evita.
cdgraham,

2
Si prega di usare critiche costruttive . Quando dico più spazio per gli errori di battitura, intendo quando definisci originariamente i valori enum Color1, che un compilatore non può catturare poiché probabilmente sarebbe ancora un nome "valido". Se scrivo RED, GREENe così via usando una classe enum, non è possibile risolverlo enum Bananaperché richiede di specificare Color1::REDper accedere al valore (l'argomento namespace). Ci sono ancora bei momenti da usare enum, ma il comportamento dello spazio dei nomi di un enum classpuò spesso essere molto utile.
cdgraham,

20

Le enumerazioni vengono utilizzate per rappresentare un set di valori interi.

La classparola chiave dopo la enumspecifica che l'enumerazione è fortemente tipizzata e che i suoi enumeratori hanno ambito. In questo modo le enumclassi prevengono l'abuso accidentale di costanti.

Per esempio:

enum class Animal{Dog, Cat, Tiger};
enum class Pets{Dog, Parrot};

Qui non possiamo mescolare i valori di animali e animali domestici.

Animal a = Dog;       // Error: which DOG?    
Animal a = Pets::Dog  // Pets::Dog is not an Animal

7

Le domande frequenti su C ++ 11 menzionano i seguenti punti:

Gli enum convenzionali vengono convertiti in modo implicito in int, causando errori quando qualcuno non desidera che un'enumerazione funzioni come un numero intero.

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;
}

gli enum convenzionali esportano i loro enumeratori nell'ambito circostante, causando scontri tra nomi.

// 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
};

Il tipo sottostante di un enum non può essere specificato, causando confusione, problemi di compatibilità e rende impossibile la dichiarazione anticipata.

// 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;
}

C ++ 11 consente anche di digitare enum "non di classe" . Esistono ancora problemi di inquinamento dello spazio dei nomi, ecc. Dai un'occhiata alle risposte pertinenti che esistevano da molto tempo prima di questo ..
user2864740

7
  1. non convertire implicitamente in int
  2. può scegliere quale tipo di base
  3. Spazio dei nomi ENUM per evitare l'inquinamento
  4. Rispetto alla classe normale, può essere dichiarato in avanti, ma non ha metodi

2

Vale la pena notare, oltre a queste altre risposte, che C ++ 20 risolve uno dei problemi che enum classha: la verbosità. Immaginando un ipotetico enum class, Color.

void foo(Color c)
  switch (c) {
    case Color::Red: ...;
    case Color::Green: ...;
    case Color::Blue: ...;
    // etc
  }
}

Questo è dettagliato rispetto alla semplice enumvariazione, in cui i nomi sono nell'ambito globale e quindi non è necessario avere il prefisso Color::.

Tuttavia, in C ++ 20 possiamo usare using enumper introdurre tutti i nomi in un enum nell'ambito corrente, risolvendo il problema.

void foo(Color c)
  using enum Color;
  switch (c) {
    case Red: ...;
    case Green: ...;
    case Blue: ...;
    // etc
  }
}

Quindi ora non c'è motivo di non usarlo enum class.


1

Perché, come detto in altre risposte, l'enum di classe non è implicitamente convertibile in int / bool, aiuta anche ad evitare il codice errato come:

enum MyEnum {
  Value1,
  Value2,
};
...
if (var == Value1 || Value2) // Should be "var == Value2" no error/warning

2
Per completare il mio commento precedente, nota che gcc ora ha un avviso chiamato -Wint-in-bool-context che rileverà esattamente questo tipo di errori.
Arnaud,

0

Una cosa che non è stata esplicitamente menzionata: la funzione scope offre un'opzione per avere lo stesso nome per un metodo enum e class. Per esempio:

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
};
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.