Escuché a algunas personas recomendando usar clases enum en C ++ debido a su tipo de seguridad .
Pero, ¿qué significa esto realmente?
Escuché a algunas personas recomendando usar clases enum en C ++ debido a su tipo de seguridad .
Pero, ¿qué significa esto realmente?
Respuestas:
C ++ tiene dos tipos de enum
:
enum class
esenum
sAquí hay un par de ejemplos de cómo declararlos:
enum class Color { red, green, blue }; // enum class
enum Animal { dog, cat, bird, human }; // plain enum
¿Cuál es la diferencia entre dos?
enum class
es - los nombres de los enumeradores son locales para la enumeración y sus valores no se convierten implícitamente a otros tipos (como otro enum
o int
)
Sin formato enum
: donde los nombres de los enumeradores están en el mismo ámbito que la enumeración y sus valores se convierten implícitamente en enteros y otros tipos
Ejemplo:
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
Deben preferirse porque causan menos sorpresas que podrían conducir a errores.
A
con estado y creo una enum class State { online, offline };
como hija de clase A
, me gustaría hacer state == online
controles dentro de en A
lugar de state == State::online
... ¿es eso posible?
enum class
era eliminarla.
Color color = Color::red
?
if (color == Card::red_card)
línea, 4 líneas más tarde que el comentario (que veo ahora se aplica a la primera mitad del bloque). 2 líneas del bloque dan los ejemplos incorrectos . Las primeras 3 líneas no son un problema. El "bloqueo completo es por qué las enumeraciones simples son malas" me arrojó, ya que pensé que querías decir que algo andaba mal con ellos también. Ahora veo, es solo un montaje. En cualquier caso, gracias por los comentarios.
De las preguntas frecuentes de C ++ 11 de Bjarne Stroustrup :
Los
enum class
es ("nuevas enumeraciones", "enumeraciones fuertes") abordan tres problemas con las enumeraciones tradicionales de C ++:
- las enumeraciones convencionales se convierten implícitamente en int, causando errores cuando alguien no quiere que una enumeración actúe como un entero.
- las enumeraciones convencionales exportan sus enumeradores al ámbito circundante, lo que provoca conflictos de nombres.
- el tipo subyacente de un
enum
no se puede especificar, lo que causa confusión, problemas de compatibilidad y hace imposible la declaración directa.Las nuevas enumeraciones son "enum class" porque combinan aspectos de enumeraciones tradicionales (valores de nombres) con aspectos de clases (miembros con ámbito y ausencia de conversiones).
Entonces, como lo mencionaron otros usuarios, las "enumeraciones fuertes" harían el código más seguro.
El tipo subyacente de un "clásico" enum
será un tipo entero lo suficientemente grande como para ajustarse a todos los valores de enum
; esto suele ser un int
. Además, cada tipo enumerado debe ser compatible con char
un tipo entero con signo / sin signo.
Esta es una descripción amplia de lo enum
que debe ser un tipo subyacente, por lo que cada compilador tomará sus propias decisiones sobre el tipo subyacente del clásico enum
y, a veces, el resultado podría ser sorprendente.
Por ejemplo, he visto código como este muchas veces:
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?
};
En el código anterior, algunos codificador ingenuo es pensar que el compilador almacenará los E_MY_FAVOURITE_FRUITS
valores en un tipo sin signo de 8 bits ... pero no hay garantía de ello: el compilador puede elegir unsigned char
o int
, o short
, cualquiera de esos tipos son lo suficientemente grandes como para caber toda la valores vistos en el enum
. Agregar el campo E_MY_FAVOURITE_FRUITS_FORCE8
es una carga y no obliga al compilador a hacer ningún tipo de elección sobre el tipo subyacente deenum
.
Si hay algún código que dependa del tamaño de letra y / o asuma que E_MY_FAVOURITE_FRUITS
letra sería de cierto ancho (por ejemplo: rutinas de serialización), este código podría comportarse de maneras extrañas dependiendo de los pensamientos del compilador.
Y para empeorar las cosas, si algún compañero de trabajo agrega descuidadamente un nuevo valor a nuestro enum
:
E_DEVIL_FRUIT = 0x100, // New fruit, with value greater than 8bits
¡El compilador no se queja! Simplemente cambia el tamaño del tipo para que se ajuste a todos los valores del enum
(suponiendo que el compilador estaba usando el tipo más pequeño posible, lo cual es una suposición que no podemos hacer). Esta adición simple y descuidada a laenum
podría romper sutilmente el código relacionado.
Dado que C ++ 11 es posible especificar el tipo subyacente para enum
y enum class
(gracias rdb ), este problema se soluciona perfectamente:
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
};
Especificando el tipo subyacente si un campo tiene una expresión fuera del rango de este tipo, el compilador se quejará en lugar de cambiar el tipo subyacente.
Creo que esta es una buena mejora de seguridad.
Entonces, ¿ por qué se prefiere la clase enum sobre la simple enum? , si podemos elegir el tipo subyacente para las enum class
enumeraciones scoped ( enum
) y unscoped ( ), ¿qué más hace enum class
una mejor elección ?:
int
.La ventaja básica de usar la clase enum sobre las enumeraciones normales es que puede tener las mismas variables de enumeración para 2 enumeraciones diferentes y aún así puede resolverlas (lo cual ha sido mencionado como tipo seguro por OP)
Por ejemplo:
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 cuanto a las enumeraciones básicas, el compilador no podrá distinguir si red
se refiere al tipo Color1
o Color2
como se muestra en la siguiente declaración.
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 }
, obviando fácilmente los problemas de espacio de nombres. El argumento del espacio de nombres es uno de los tres mencionados aquí que no compro en absoluto.
enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }
es comparable a la clase de enumeración: enum class Color1 { RED, GREEN, BLUE }
. El acceso es similar: COLOR1_RED
vs Color1::RED
, pero la versión Enum requiere que escriba "COLOR1" en cada valor, lo que da más espacio para errores tipográficos, lo que evita el comportamiento del espacio de nombres de una clase enum.
enum Color1
, que un compilador no puede detectar, ya que probablemente todavía sería un nombre "válido". Si escribo RED
, GREEN
y así sucesivamente usando una clase enum, entonces no puede resolverlo enum Banana
porque requiere que especifiques Color1::RED
para acceder al valor (el argumento del espacio de nombres). Todavía hay buenos momentos para usar enum
, pero el comportamiento del espacio de nombres de un a enum class
menudo puede ser muy beneficioso.
Las enumeraciones se utilizan para representar un conjunto de valores enteros.
La class
palabra clave después de enum
especifica que la enumeración está fuertemente tipada y sus enumeradores tienen un alcance. De esta forma, las enum
clases evitan el mal uso accidental de las constantes.
Por ejemplo:
enum class Animal{Dog, Cat, Tiger};
enum class Pets{Dog, Parrot};
Aquí no podemos mezclar los valores de animales y mascotas.
Animal a = Dog; // Error: which DOG?
Animal a = Pets::Dog // Pets::Dog is not an Animal
Las preguntas frecuentes de C ++ 11 mencionan los siguientes puntos:
las enumeraciones convencionales se convierten implícitamente en int, causando errores cuando alguien no quiere que una enumeración actúe como un entero.
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;
}
las enumeraciones convencionales exportan sus enumeradores al ámbito circundante, lo que provoca conflictos de nombres.
// 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
};
El tipo subyacente de una enumeración no se puede especificar, lo que causa confusión, problemas de compatibilidad y hace imposible la declaración directa.
// 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 señalar, además de estas otras respuestas, que C ++ 20 resuelve uno de los problemas que enum class
tiene: verbosidad. Imaginando un hipotético enum class
, Color
.
void foo(Color c)
switch (c) {
case Color::Red: ...;
case Color::Green: ...;
case Color::Blue: ...;
// etc
}
}
Esto es detallado en comparación con la enum
variación simple , donde los nombres están en el ámbito global y, por lo tanto, no necesitan tener el prefijo Color::
.
Sin embargo, en C ++ 20 podemos usar using enum
para introducir todos los nombres en una enumeración al alcance actual, resolviendo el problema.
void foo(Color c)
using enum Color;
switch (c) {
case Red: ...;
case Green: ...;
case Blue: ...;
// etc
}
}
Así que ahora, no hay razón para no usar enum class
.
Porque, como se dijo en otras respuestas, la enumeración de la clase no es implícitamente convertible a int / bool, también ayuda a evitar códigos con errores como:
enum MyEnum {
Value1,
Value2,
};
...
if (var == Value1 || Value2) // Should be "var == Value2" no error/warning
Una cosa que no se ha mencionado explícitamente: la función de alcance le brinda la opción de tener el mismo nombre para un método de enumeración y clase. Por ejemplo:
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
};