Cuando alguien afirma que una construcción de programación es "malvada", está tratando de disuadirlo de que piense por sí mismo.
Pete Becker
3
@NicolBolas: Esta es más una pregunta retórica para proporcionar una respuesta de preguntas frecuentes (si esto es realmente una pregunta frecuente es una historia diferente).
David Rodríguez - dribeas
@David, hay una discusión sobre si esto debería ser una pregunta frecuente o no, que comienza aquí . Entrada de bienvenida.
sbi
17
@PeteBecker A veces simplemente intentan protegerte de ti mismo.
piccy
geeksforgeeks.org/... Este es también un lugar bueno para entender enumvs enum class.
mr_azad
Respuestas:
473
C ++ tiene dos tipos de enum:
enum classes
Plain enums
Aquí hay un par de ejemplos de cómo declararlos:
enumclassColor{ red, green, blue };// enum classenumAnimal{ dog, cat, bird, human };// plain enum
¿Cuál es la diferencia entre dos?
enum classes - los nombres de los enumeradores son locales para la enumeración y sus valores no se convierten implícitamente a otros tipos (como otro enumo 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:
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;}
Conclusión:
enum classDeben preferirse porque causan menos sorpresas que podrían conducir a errores.
Buen ejemplo ... ¿hay alguna manera de combinar la seguridad de tipo de la versión de clase con la promoción de espacio de nombres de la versión enum? Es decir, si tengo una clase Acon estado y creo una enum class State { online, offline };como hija de clase A, me gustaría hacer state == onlinecontroles dentro de en Alugar de state == State::online... ¿es eso posible?
marca el
31
No La promoción del espacio de nombres es una mala cosa ™ y la mitad de la justificación enum classera eliminarla.
Cachorro
10
En C ++ 11, también puede usar enumeraciones escritas explícitamente, como enum Animal: unsigned int {dog, deer, cat, bird}
Blasius Secundus
3
@Cat Plus Plus Entiendo que @Oleksiy dice que es malo. Mi pregunta no era si Oleksiy pensaba que era malo. Mi pregunta fue una solicitud para detallar qué tiene de malo. Específicamente, ¿por qué Oleksiy, por ejemplo, se considera malo Color color = Color::red?
chux - Restablece a Mónica el
99
@Cat Plus Plus Entonces, el ejemplo incorrecto no ocurre hasta la 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.
Los enum classes ("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 enumno 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" enumserá 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 charun tipo entero con signo / sin signo.
Esta es una descripción amplia de lo enumque debe ser un tipo subyacente, por lo que cada compilador tomará sus propias decisiones sobre el tipo subyacente del clásico enumy, 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_FRUITSvalores en un tipo sin signo de 8 bits ... pero no hay garantía de ello: el compilador puede elegir unsigned charo 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_FORCE8es 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 enumy enum class(gracias rdb ), este problema se soluciona perfectamente:
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 classenumeraciones scoped ( enum) y unscoped ( ), ¿qué más hace enum classuna mejor elección ?:
Supongo que también podemos restringir el tipo de base de enumeración para enumeraciones regulares, siempre que tengamos C ++ 11
Sagar Padhye
11
Lo siento, pero esta respuesta es incorrecta. "enum class" no tiene nada que ver con la capacidad de especificar el tipo. Esa es una característica independiente que existe tanto para las enumeraciones regulares como para las clases de enumeración.
rdb
14
Este es el trato: * Las clases Enum son una nueva característica en C ++ 11. * Las enumeraciones escritas son una nueva característica en C ++ 11. Estas son dos nuevas características independientes no relacionadas en C ++ 11. Puede usar ambos, o puede usar cualquiera, o ninguno.
rdb
2
Creo que Alex Allain proporciona la explicación simple más completa que he visto en este blog en [ cprogramming.com/c++11/… . La enumeración tradicional era buena para usar nombres en lugar de valores enteros y evitar el uso del preprocesador #defines, que era una buena cosa: agregaba claridad. la clase enum elimina el concepto de un valor numérico del enumerador e introduce el alcance y la tipificación fuerte que aumenta (bueno, puede aumentar :-) la corrección del programa. Te acerca un paso más al pensamiento orientado a objetos.
Jon Spencer
2
Por otro lado, siempre es divertido cuando revisas el código y de repente sucede One Piece .
Justin Time - Restablece a Monica el
47
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:
enumclassColor1{ red, green, blue };//this will compileenumclassColor2{ red, green, blue };enumColor1{ red, green, blue };//this will not compile enumColor2{ red, green, blue };
En cuanto a las enumeraciones básicas, el compilador no podrá distinguir si redse refiere al tipo Color1o Color2como se muestra en la siguiente declaración.
enumColor1{ red, green, blue };enumColor2{ red, green, blue };int x = red;//Compile time error(which red are you refering to??)
@Oleksiy Ohh, no leí tu pregunta correctamente. Considerar es como un complemento para aquellos que no lo sabían.
Saksham
¡está bien! Casi me olvido de esto
Oleksiy
por supuesto, escribirías 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.
Jo So
2
@Jo Entonces, esa solución es una solución innecesaria. Enumeración: 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_REDvs 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.
cdgraham
2
Por favor, use la crítica constructiva . Cuando digo más espacio para los errores tipográficos, quiero decir cuando definiste originalmente los valores de enum Color1, que un compilador no puede detectar, ya que probablemente todavía sería un nombre "válido". Si escribo RED, GREENy así sucesivamente usando una clase enum, entonces no puede resolverlo enum Bananaporque requiere que especifiques Color1::REDpara 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 classmenudo puede ser muy beneficioso.
cdgraham
20
Las enumeraciones se utilizan para representar un conjunto de valores enteros.
La classpalabra clave después de enumespecifica que la enumeración está fuertemente tipada y sus enumeradores tienen un alcance. De esta forma, las enumclases evitan el mal uso accidental de las constantes.
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};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;}
las enumeraciones convencionales exportan sus enumeradores al ámbito circundante, lo que provoca conflictos de nombres.
// 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};
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.
C ++ 11 permite escribir también enumeraciones "no de clase" . Los problemas de contaminación del espacio de nombres, etc., todavía existen. Eche un vistazo a las respuestas relevantes que existieron mucho antes de esta ...
user2864740
7
no convertir implícitamente a int
puede elegir qué tipo subyace
Espacio de nombres ENUM para evitar que sucedan contaminantes
En comparación con la clase normal, puede declararse hacia adelante, pero no tiene métodos
Vale la pena señalar, además de estas otras respuestas, que C ++ 20 resuelve uno de los problemas que enum classtiene: verbosidad. Imaginando un hipotético enum class, Color.
Esto es detallado en comparación con la enumvariació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 enumpara introducir todos los nombres en una enumeración al alcance actual, resolviendo el problema.
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:
enumMyEnum{Value1,Value2,};...if(var ==Value1||Value2)// Should be "var == Value2" no error/warning
Para completar mi comentario anterior, tenga en cuenta que gcc ahora tiene una advertencia llamada -Wint-in-bool-context que detectará exactamente este tipo de errores.
Arnaud
0
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:
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
.Respuestas:
C ++ tiene dos tipos de
enum
:enum class
esenum
sAquí hay un par de ejemplos de cómo declararlos:
¿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 otroenum
oint
)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 tiposEjemplo:
Conclusión:
enum class
Deben preferirse porque causan menos sorpresas que podrían conducir a errores.fuente
A
con estado y creo unaenum class State { online, offline };
como hija de claseA
, me gustaría hacerstate == online
controles dentro de enA
lugar destate == 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 :
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 deenum
; esto suele ser unint
. Además, cada tipo enumerado debe ser compatible conchar
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ásicoenum
y, a veces, el resultado podría ser sorprendente.Por ejemplo, he visto código como este muchas veces:
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 elegirunsigned char
oint
, oshort
, cualquiera de esos tipos son lo suficientemente grandes como para caber toda la valores vistos en elenum
. Agregar el campoE_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
:¡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
yenum class
(gracias rdb ), este problema se soluciona perfectamente: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 haceenum class
una mejor elección ?:int
.fuente
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:
En cuanto a las enumeraciones básicas, el compilador no podrá distinguir si
red
se refiere al tipoColor1
oColor2
como se muestra en la siguiente declaración.fuente
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
vsColor1::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 escriboRED
,GREEN
y así sucesivamente usando una clase enum, entonces no puede resolverloenum Banana
porque requiere que especifiquesColor1::RED
para acceder al valor (el argumento del espacio de nombres). Todavía hay buenos momentos para usarenum
, pero el comportamiento del espacio de nombres de un aenum class
menudo puede ser muy beneficioso.Las enumeraciones se utilizan para representar un conjunto de valores enteros.
La
class
palabra clave después deenum
especifica que la enumeración está fuertemente tipada y sus enumeradores tienen un alcance. De esta forma, lasenum
clases evitan el mal uso accidental de las constantes.Por ejemplo:
Aquí no podemos mezclar los valores de animales y mascotas.
fuente
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.
las enumeraciones convencionales exportan sus enumeradores al ámbito circundante, lo que provoca conflictos de nombres.
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.
.
.
fuente
fuente
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éticoenum class
,Color
.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 prefijoColor::
.Sin embargo, en C ++ 20 podemos usar
using enum
para introducir todos los nombres en una enumeración al alcance actual, resolviendo el problema.Así que ahora, no hay razón para no usar
enum class
.fuente
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:
fuente
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:
fuente