Ho sentito alcune persone raccomandare di usare le classi enum in C ++ a causa della loro sicurezza del tipo .
Ma cosa significa veramente?
Ho sentito alcune persone raccomandare di usare le classi enum in C ++ a causa della loro sicurezza del tipo .
Ma cosa significa veramente?
Risposte:
C ++ ha due tipi di enum
:
enum class
esenum
sEcco 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 class
es - i nomi degli enumeratori sono locali nell'enum e i loro valori non vengono convertiti implicitamente in altri tipi (come un altro enum
o int
)
Plain enum
s - 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;
}
enum class
es dovrebbe essere preferito perché causano meno sorprese che potrebbero potenzialmente portare a bug.
A
con stato e ne creo una enum class State { online, offline };
come figlia di classe A
, mi piacerebbe fare state == online
controlli all'interno A
invece di state == State::online
... è possibile?
enum class
stata eliminarla.
Color color = Color::red
.
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.
Dalle FAQ C ++ 11 di Bjarne Stroustrup :
Gli
enum class
es ("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
enum
non 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" enum
deve 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 char
un tipo intero con o senza segno.
Questa è un'ampia descrizione di ciò enum
che deve essere un tipo sottostante, quindi ogni compilatore prenderà le proprie decisioni sul tipo sottostante del classico enum
e 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_FRUITS
valori in un tipo a 8 bit senza segno ... ma non c'è alcuna garanzia al riguardo: il compilatore può scegliere unsigned char
o int
oppure 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_FRUITS
sia 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 enum
codice potrebbe spezzare sottilmente.
Poiché C ++ 11 è possibile specificare il tipo sottostante per enum
e 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 class
enumerati scoped ( ) e unscoped ( enum
) cos'altro fa enum class
una scelta migliore ?:
int
.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 red
si riferisce al tipo Color1
o Color2
come 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??)
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.
enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }
è paragonabile alla classe Enum: enum class Color1 { RED, GREEN, BLUE }
. L'accesso è simile: COLOR1_RED
vs 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.
enum Color1
, che un compilatore non può catturare poiché probabilmente sarebbe ancora un nome "valido". Se scrivo RED
, GREEN
e così via usando una classe enum, non è possibile risolverlo enum Banana
perché richiede di specificare Color1::RED
per accedere al valore (l'argomento namespace). Ci sono ancora bei momenti da usare enum
, ma il comportamento dello spazio dei nomi di un enum class
può spesso essere molto utile.
Le enumerazioni vengono utilizzate per rappresentare un set di valori interi.
La class
parola chiave dopo la enum
specifica che l'enumerazione è fortemente tipizzata e che i suoi enumeratori hanno ambito. In questo modo le enum
classi 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
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;
}
Vale la pena notare, oltre a queste altre risposte, che C ++ 20 risolve uno dei problemi che enum class
ha: 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 enum
variazione, in cui i nomi sono nell'ambito globale e quindi non è necessario avere il prefisso Color::
.
Tuttavia, in C ++ 20 possiamo usare using enum
per 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
.
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
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
};