What is an enum class in C++?

Enum Class en C++: Código Más Seguro y Moderno

14/11/2006

Valoración: 4.48 (10286 votos)

En el vasto universo de la programación en C++, constantemente buscamos herramientas que nos permitan escribir código más limpio, seguro y fácil de mantener. Una de esas joyas, introducida en el estándar C++11, es el `enum class`, también conocido como "scoped enumeration" o enumeración con ámbito. Si bien los `enum` tradicionales han sido un pilar del lenguaje durante décadas para agrupar constantes relacionadas, arrastran consigo ciertas debilidades que el `enum class` resuelve de manera elegante. En este artículo, exploraremos a fondo qué son, por qué deberías usarlos y cómo transformarán la calidad de tu código.

What is an enum in JavaScript?
An enum is a special type that represents a group of constants (unchangeable values). To create an enum, use the enum keyword, followed by the name of the enum, and separate the enum items with a comma: Note that the last item does not need a comma. It is not required to use uppercase, but often considered as good practice.
Índice de Contenido

El `enum` Tradicional y sus Desafíos

Para apreciar plenamente las ventajas del `enum class`, primero debemos entender las limitaciones de su predecesor, el `enum` convencional, a veces llamado "unscoped enum". Un `enum` nos permite asignar nombres a valores enteros, haciendo el código mucho más legible. Por ejemplo, en lugar de usar números mágicos como 0, 1 y 2 para representar niveles de dificultad, podemos hacer esto:

enum NivelDificultad { FACIL, // Valor por defecto 0 NORMAL, // Valor por defecto 1 DIFICIL // Valor por defecto 2 }; NivelDificultad miNivel = NORMAL;

Esto es mucho más expresivo que `int miNivel = 1;`. Sin embargo, esta simplicidad esconde tres problemas fundamentales que pueden introducir errores sutiles y difíciles de depurar en nuestros programas.

1. Conversión Implícita a Enteros

Los enumeradores de un `enum` tradicional se convierten implícitamente a tipos enteros (`int`, `char`, etc.). Esto puede parecer conveniente, pero abre la puerta a comparaciones sin sentido y operaciones no deseadas.

enum Color { ROJO, VERDE, AZUL }; enum Fruta { MANZANA, PERA, NARANJA }; Color miColor = ROJO; // ROJO es 0 Fruta miFruta = MANZANA; // MANZANA es 0 if (miColor == miFruta) { // ¡Esto compila y se evalúa como verdadero! // Lógica incorrecta que se ejecutará } 

El compilador no ve ningún problema en comparar un color con una fruta, porque para él, ambos son simplemente el entero `0`. Esto viola la seguridad de tipos que tanto buscamos en C++.

2. Contaminación del Espacio de Nombres (Namespace Pollution)

Quizás el problema más notorio es que los nombres de los enumeradores (`ROJO`, `VERDE`, `MANZANA`, etc.) se "filtran" al ámbito que los rodea. Esto significa que no puedes tener dos `enum` en el mismo ámbito con un enumerador que se llame igual.

// Error de compilación: 'ROJO' está redefinido enum ColorPrimario { ROJO, AMARILLO, AZUL }; enum EstadoAlerta { VERDE, AMARILLO, ROJO };

Este choque de nombres nos obliga a usar prefijos largos y engorrosos para evitar colisiones, ensuciando el código y haciéndolo menos legible.

What is an enum class in C++?
In C++, enum class is a slight strengthening of the compiler checking compared to plain old enums. Unlike conventional enums, enum classes cannot be implicitly converted to int, preventing errors when an enumeration is not meant to act as an integer.

3. Tipo Subyacente Indefinido y sin Declaración Anticipada

Con un `enum` tradicional, no puedes especificar qué tipo entero subyacente se usará para almacenar los valores (`char`, `short`, `unsigned int`, etc.). El compilador elige uno lo suficientemente grande, lo que puede ser ineficiente en términos de memoria y causar problemas de compatibilidad entre diferentes plataformas. Además, esta incapacidad para definir el tipo subyacente hace imposible realizar una declaración anticipada (forward declaration) de un `enum`, algo útil en archivos de cabecera grandes.

La Solución: `enum class`, el Enum con Ámbito y Tipado Fuerte

El `enum class` fue diseñado específicamente para solucionar todos los problemas anteriores. Su sintaxis es casi idéntica, pero con la adición de la palabra clave `class` (o `struct`, que es funcionalmente equivalente).

enum class Color { ROJO, VERDE, AZUL }; enum class Fruta { MANZANA, PERA, NARANJA };

Veamos cómo aborda cada uno de los problemas:

1. Tipado Fuerte y sin Conversiones Implícitas

Un `enum class` no se convierte implícitamente a un entero. Son tipos distintos y no compatibles sin una conversión explícita. El código problemático que vimos antes ahora fallará en tiempo de compilación, que es exactamente lo que queremos.

Color miColor = Color::ROJO; Fruta miFruta = Fruta::MANZANA; // ¡ERROR DE COMPILACIÓN! No se puede comparar Color y Fruta. if (miColor == miFruta) { // ... } int valorColor = miColor; // ¡ERROR DE COMPILACIÓN! No hay conversión implícita a int.

Si realmente necesitas el valor entero, debes solicitarlo de forma explícita, dejando clara tu intención en el código:

int valorColor = static_cast<int>(miColor); // Esto sí es válido. El valor será 0.

Esta característica por sí sola elimina una categoría entera de errores lógicos.

2. Ámbito Propio (Scoped)

Los enumeradores de un `enum class` viven dentro del ámbito de la enumeración. Para acceder a ellos, debes usar el nombre del `enum class` seguido del operador de resolución de ámbito `::`.

enum class ColorPrimario { ROJO, AMARILLO, AZUL }; enum class EstadoAlerta { VERDE, AMARILLO, ROJO }; // ¡Esto ahora compila sin problemas! ColorPrimario miColor = ColorPrimario::ROJO; EstadoAlerta miAlerta = EstadoAlerta::ROJO;

Esto resuelve completamente el problema de la contaminación del espacio de nombres. El código es más claro, ya que siempre es evidente a qué grupo pertenece un valor (`ColorPrimario::ROJO` es inequívoco).

How are enums indexed in C++?
Enum's are indexed starting at 0 and incrementing by one for each value (how odd that 0 means false and 1 means true in C++, and these are the indexes of true and false in the enum above :o). You can specify otherwise if you want to however by assigning elements a specific index.

3. Especificación del Tipo Subyacente

Con `enum class`, tienes control total sobre el tipo de dato que se usará para almacenar los valores. Esto se hace especificándolo después del nombre del `enum`.

// Usamos un solo byte sin signo para almacenar los valores enum class Opciones: unsigned char { OPCION_A = 0x01, OPCION_B = 0x02, OPCION_C = 0x04 };

Esto tiene dos grandes beneficios:

  • Control de memoria: Puedes asegurarte de que tu `enum` ocupe la menor cantidad de memoria posible, algo crucial en sistemas embebidos o aplicaciones de alto rendimiento.
  • Seguridad en la compilación: El compilador verificará si los valores que asignas caben en el tipo subyacente que has definido.
// El compilador generará un error aquí porque 0x120 (288) no cabe en un unsigned char (0-255) enum class CosasDeByte: unsigned char { Cosa1 = 0x01, Cosa2 = 0x02, CosaGrande = 0x120 // ¡Error de compilación! };

Además, al poder especificar el tipo subyacente, ¡ahora es posible realizar declaraciones anticipadas!

// En un archivo .h enum class CodigoError: long; void manejarError(CodigoError codigo); // En un archivo .cpp enum class CodigoError: long { OK = 0, NO_ENCONTRADO = 404, ERROR_SERVIDOR = 500 }; void manejarError(CodigoError codigo) { /* ... */ }

Tabla Comparativa: `enum` vs. `enum class`

Para resumir las diferencias clave, aquí tienes una tabla comparativa:

Característica`enum` (Tradicional)`enum class` (C++11)
TipadoDébil (débilmente tipado)Fuerte (fuertemente tipado)
Conversión a `int`Implícita y automáticaNo permitida implícitamente, requiere `static_cast`
Ámbito de los enumeradoresEl mismo que el del `enum` (contamina el namespace)Propio del `enum class` (se accede con `::`)
Especificar tipo subyacenteNo estándar antes de C++11Sí, de forma explícita (`: tipo`)
Declaración anticipadaNo es posibleSí, si se especifica el tipo subyacente

Preguntas Frecuentes (FAQ)

¿Por qué mi `enum class` no funciona en un `switch` como antes?

Sí funciona, pero debes recordar que los valores ahora tienen ámbito. En lugar de escribir `case MI_VALOR`, debes calificarlo con el nombre del `enum class`: `case MiEnum::MI_VALOR`.

enum class Estado { Activo, Inactivo, Pendiente }; void procesarEstado(Estado e) { switch (e) { case Estado::Activo: // ... break; case Estado::Inactivo: // ... break; case Estado::Pendiente: // ... break; } }

¿Es `enum struct` lo mismo que `enum class`?

Sí, son absolutamente idénticos en funcionalidad y comportamiento. La elección entre usar la palabra clave `class` o `struct` es puramente estilística y no afecta al código generado. `enum class` es la forma más común que verás en la mayoría de las guías de estilo.

¿Cuándo debería usar un `enum` tradicional en lugar de un `enum class`?

La regla general en C++ moderno es: prefiere siempre `enum class`. Las únicas situaciones en las que un `enum` tradicional podría ser necesario es al interactuar con bibliotecas o APIs antiguas escritas en C que esperan un tipo que se convierta implícitamente a `int`. Incluso en esos casos, a menudo es mejor usar un `enum class` internamente y hacer una conversión explícita en la frontera con la API antigua.

Conclusión

El `enum class` no es simplemente una alternativa sintáctica al `enum` tradicional; es una mejora fundamental que promueve un código más seguro, más claro y más fácil de mantener. Al proporcionar seguridad de tipos, un ámbito bien definido y control sobre el almacenamiento subyacente, `enum class` elimina una clase entera de errores comunes y se alinea perfectamente con la filosofía del C++ moderno. Adoptar su uso en tus proyectos es un paso sencillo pero poderoso para escribir código más robusto y profesional.

Si quieres conocer otros artículos parecidos a Enum Class en C++: Código Más Seguro y Moderno puedes visitar la categoría Juegos.

Subir