Las enumeraciones permiten asignar nombres legibles a un conjunto de valores enteros. En la versión clásica de C++ (heredada de C), usamos enum (no-escopado), donde los nombres de sus elementos se “derraman” en el ámbito donde se definen. Esto significa que si defines enum Color { Rojo, Verde };, puedes usar Rojo directamente en tu código, pero también provocas que cualquier otra variable llamada Rojo en ese mismo ámbito falle por colisión de nombres. Además, estos enums tienen una conversión implícita a int, lo que permite comparaciones accidentales como if (mi_color == 5), un error lógico difícil de detectar a simple vista.
Para solucionar esto, C++11 [C++11] introdujo enum class (enumeraciones escopadas). Con ellas, los enumeradores viven dentro del ámbito del nombre del tipo (ej. Color::Rojo), lo que evita la contaminación del espacio de nombres. Además, son fuertemente tipados: el compilador no te permitirá comparar un Color con un int sin un cast explícito, protegiéndote de errores de lógica.
Puedes controlar cuánta memoria ocupa cada enumeración especificando su tipo subyacente (underlying type). Por ejemplo, puedes usar uint8_t para asegurar que el enum solo ocupe un byte, lo cual es vital en sistemas con memoria limitada o para asegurar la compatibilidad en la ABI.
Cuando necesitas trabajar con el valor numérico de un enum class, puedes usar static_cast o, si trabajas con C++23 [C++23], la función std::to_underlying para una sintaxis mucho más limpia.
#include <iostream>
#include <cstdint>
#include <type_traits>
#include <utility> // Para std::to_underlying en C++23
// 1. Enum no-escopado (estilo C)
// Los nombres Rojo, Verde y Azul pertenecen al ámbito global/local actual.
enum Color {
Rojo,
Verde,
Azul
};
// 2. Enum escopado (estilo C++11)
// Los nombres están protegidos dentro de ColorStatus.
// Especificamos uint8_t para que ocupe exactamente 1 byte.
enum class ColorStatus : uint8_t {
Ok,
Error,
Invalido
};
// 3. Enum escopado para uso con operaciones de bits (flags)
// Usamos un enum class para mantener la seguridad, pero sobrecargamos
// el operador OR (|) para poder combinar permisos de forma natural.
enum class Permission : uint8_t {
Ninguno = 0,
Lectura = 1 << 0, // 1
Escritura = 1 << 1, // 2
Ejecucion = 1 << 2 // 4
};
// Sobrecarga de operador para permitir combinar permisos con el operador '|'
inline Permission operator|(Permission a, Permission b) {
// Casteamos a su tipo base para hacer la operación bit a bit y volvemos a castear.
return static_cast<Permission>(static_cast<uint8_t>(a) | static_cast<uint8_t>(b));
}
int main() {
// --- Caso Unscoped ---
// La conversión a int es automática y silenciosa.
int valor_color = Rojo;
std::cout << "Color (unscoped) como int: " << valor_color << "\n";
// --- Caso Scoped ---
ColorStatus status = ColorStatus::Ok;
// Esto daría error de compilación: if (status == 0)
// Porque ColorStatus no se convierte a int automáticamente.
if (status == ColorStatus::Ok) {
std::cout << "Estado: OK\n";
}
// En C++23, std::to_underlying es la forma limpia de obtener el valor numérico.
auto raw_status = std::to_underlying(status);
std::cout << "Valor real de status (uint8_t): " << static_cast<int>(raw_status) << "\n";
// --- Caso de Permisos (Bitmask) ---
Permission p = Permission::Lectura | Permission::Escritura;
// Para verificar si un bit está activo, necesitamos el cast al tipo subyacente.
uint8_t p_raw = static_cast<uint8_t>(p);
if (p_raw & static_cast<uint8_t>(Permission::Lectura)) {
std::cout << "Permiso de lectura detectado\n";
}
return 0;
}
Análisis del código
enum Color: Al ser un enum no-escopado,Rojoes una constante visible directamente. Si intentas declarar otra variable llamadaRojoen el mismo bloque, el compilador fallará. Además,int valor_color = Rojo;funciona sin necesidad destatic_castporque el compilador realiza una conversión implícita.enum class ColorStatus : uint8_t: Aquí, el nombreOkno existe por sí solo; debes usarColorStatus::Ok. Al heredar deuint8_t, el tamaño en memoria está garantizado. Si intentas compararstatus == 0, el compilador te dará un error porque losenum classno se convierten a enteros por accidente.Permissiony el operador|: Como losenum classson tipos fuertes, no puedes usar|directamente sobre ellos para combinar bits. Para permitir esto de forma segura, sobrecargamos el operadoroperator|. Dentro de la función, hacemos unstatic_cast<uint8_t>para realizar la operación matemática real sobre los bits y luego devolvemos el resultado convertido de nuevo al tipoPermission.std::to_underlying(status): En lugar de escribirstatic_cast<uint8_t>(status), que es ruidoso y requiere que conozcas el tipo exacto,std::to_underlying(introducido en C++23) recupera el valor base de forma genérica y limpia.
El error frecuente
Un error clásico ocurre cuando intentas definir dos enums diferentes que comparten nombres de miembros en el mismo ámbito. Esto es muy común al integrar librerías legacy.
// Código erróneo
enum Status { OK, Error };
enum Result { OK, Failure }; // Error de compilación: 'OK' redeclared
El compilador lanzará un error de redefinición porque OK ha quedado “atrapado” en el espacio de nombres global debido a que el primer enum no está escopado. Usar siempre enum class elimina este problema por completo.
N° 134