Para qué sirve un enum
Una struct agrupa varios valores relacionados en un solo objeto. Un enum resuelve un problema distinto: da nombres a un conjunto pequeño y fijo de opciones. En lugar de recordar que 0 significa "rojo", 1 significa "verde" y 2 significa "azul", escribes Color::Red y el compilador te mantiene honesto.
Recurrir a un enum siempre que una variable solo pueda tomar uno de un puñado de estados con nombre —el color de un semáforo, el palo de una carta, un estado de conexión— hace que el código se documente a sí mismo y permite al compilador detectar erratas y casos faltantes que los enteros sin más nunca atraparían.
Declarar un enum
El C++ moderno tiene dos variantes. Empieza por la que deberías usar casi siempre: el enum class con ámbito. Enumeras los nombres, y se accede a cada enumerador a través del nombre del enum con :::
Fíjate en que escribes Color::Green, nunca un Green a secas. Los valores en sí son solo etiquetas: los comparas, los asignas y los pasas de un lado a otro, pero rara vez te importa el número subyacente. Por defecto Red es 0, Green es 1 y Blue es 2, contando hacia arriba desde cero.
enum simple vs enum class
El enum más antiguo y sin ámbito (sin la palabra clave class) vuelca sus nombres directamente en el ámbito que lo rodea y se convierte a int por sí solo. Eso suena cómodo, pero causa dos problemas reales:
enum Color { Red, Green, Blue };
enum Fruit { Apple, Banana, Red }; // error: 'Red' already declared
enum Status { Active, Inactive };
int x = Active; // compiles silently - is this what you meant?
if (Active == Banana) { // compares unrelated enums via int - allowed!
}
Como los enumeradores simples son nombres globales, dos enums pueden colisionar solo por compartir una etiqueta. Y como degeneran a int, el compilador compara tan tranquilo valores de enums totalmente ajenos entre sí. Un enum class con ámbito arregla ambas cosas: los nombres viven dentro del tipo, y el tipo no se convertirá silenciosamente en un int:
La regla práctica: usa enum class por defecto. Solo recurre a un enum simple cuando quieras específicamente la conversión implícita a int, como en las viejas constantes de banderas al estilo C.
Valores personalizados y el tipo subyacente
Puedes asignar números explícitos a los enumeradores. Los que dejes sin asignar siguen contando hacia arriba desde el anterior, lo cual resulta útil para cosas como códigos de estado HTTP o banderas de bits:
Todo enum está respaldado por un tipo entero —int por defecto—. Puedes fijarlo a un tipo más pequeño cuando te importa el tamaño, por ejemplo al almacenar muchos enums en una estructura compacta o al adaptarte a un formato de transmisión:
Elegir el tipo subyacente también garantiza el rango en el que deben caber tus valores: un enum uint8_t no puede contener un valor por encima de 255.
Convertir entre enum e int
Un enum class con ámbito nunca se convierte implícitamente, que es justo el objetivo. Cuando de verdad necesitas el número —para imprimirlo, indexar un array o leerlo de un archivo— recurre a static_cast. Pasar de enum a int siempre es seguro:
Convertir un int de vuelta a un enum es la dirección peligrosa. La conversión no comprueba que el número corresponda a un enumerador real: te entregará un valor que técnicamente está fuera del conjunto de nombres del enum:
Suit s = static_cast<Suit>(2); // fine - that's Clubs
Suit bad = static_cast<Suit>(99); // compiles, but 99 is not a valid Suit
// using `bad` in a switch or as an array index is a lurking bug
Si el entero viene de la entrada del usuario o de un archivo, valida tú mismo el rango antes de convertirlo, o crearás un valor que ningún case maneja jamás: una fuente sutil de comportamiento indefinido más adelante.
Usar enums con switch
Como un enum es "uno de un conjunto fijo", encaja a la perfección con un switch. Cuando cubres todos los enumeradores, muchos compiladores te avisarán si más tarde añades un valor nuevo y olvidas manejarlo: una seguridad gratis que no obtienes con enteros sin más:
Un detalle a tener en cuenta: no hay una forma integrada de imprimir el nombre de un enumerador. cout << TrafficLight::Red no compilará para un enum con ámbito, e incluso para un enum simple imprime el número, no "Red". Un pequeño switch o una tabla de búsqueda como la de arriba es la forma habitual de convertir un enum en una cadena legible para humanos.
Siguiente: Excepciones
Los enums y las structs te permiten modelar qué aspecto tienen tus datos. Pero los programas reales también tienen que lidiar con que las cosas salgan mal: un archivo que no abre, un número que no se parsea, un valor fuera de rango. C++ gestiona esas rutas de fallo con excepciones, y ese es el tema de la siguiente página.
Preguntas frecuentes
¿Cuál es la diferencia entre enum y enum class en C++?
Un enum simple filtra sus nombres al ámbito que lo rodea y se convierte implícitamente a int, lo que provoca colisiones de nombres y comparaciones accidentales. Un enum class con ámbito mantiene sus nombres dentro del enum (Color::Red) y se niega a convertirse a int sin una conversión explícita. Prefiere enum class en el C++ moderno: es seguro respecto a tipos y evita las trampas clásicas.
¿Cómo se convierte un enum de C++ a un int?
Un enum simple se convierte implícitamente, así que int n = Red; funciona sin más. Un enum class con ámbito requiere una conversión explícita: int n = static_cast<int>(Color::Red);. Para hacerlo al revés, convierte el int de vuelta: Color c = static_cast<Color>(2);, pero ten cuidado: en tiempo de ejecución no se comprueba que el valor sea un enumerador válido.
¿En qué valor empieza el primer enum de C++?
Por defecto el primer enumerador es 0, y cada uno siguiente vale uno más que el anterior. Así que en enum class Level { Low, Mid, High };, Low es 0, Mid es 1 y High es 2. Puedes asignar valores explícitos a cualquiera de ellos para anular este comportamiento.