Enumeraciones en C++0x

febrero 21, 2010

Una de las críticas habituales de C++ está relacionada con las enumeraciones y se debe principalmente a la herencia de C. Para resolver estas cuestiones, en C++0x se añaden varias nuevas formas de definir enumeraciones. Estos cambios pueden no parecer especialmente significativos, sobre todo comparándolos con otros, pero en mi opinión es importante conocer la nueva sintaxis, aunque solo sea para entender la documentación en la que aparecen.

Enumeraciones con ámbito

En C++98 el siguiente código no es válido:

enum color {rojo, verde, azul, naranja, amarillo};
enum fruta {pera, naranja, manzana}; //error

La razón es que el enumerador naranja aparece en dos enumeraciones diferentes, y es por lo tanto un identificador duplicado.
En todos los demás contextos de C++ donde se declara un identificador, éste se limita al ámbito (scope) en el que está declarado. Pero con las enumeraciones es diferente, los nombres de las constantes se cuelan hacia el ámbito exterior, en el que está declarada la enumeración, pues así es como funcionan en C. En el nuevo C++, C++0x, existe un nuevo tipo de enumeración, la enumeración con ámbito (scoped enumeration).
Una enumeracion con ámbito es parecida a una enumeración normal, pero se declara con enum class o enum struct, indistintamente.

enum class color {rojo, verde, azul, naranja, amarillo};
enum class fruta {pera, naranja, manzana};

Así, cada enumeración crea un ámbito en el que se definen los valores de la enumeración. Para utilizar los enumeradores debe indicarse el nombre completo:

color c = color::naranja;
fruta f = fruta::naranja;

Además, para hacer enfatizar la independencia de este tipo, y al contrario de las enumeraciones antiguas, no es convertible a entero de forma implícita (no sufre promoción integral):

int x = color::rojo; //error
int x = static_cast<int>(color::rojo); //bien

Aunque, por supuesto, puede hacerse la conversión con static_cast.

Especificación del tipo subyacente

Se llama tipo subyacente (underlying type) de una enumeración al tipo integral al que equivale en términos de representación en memoria. En C++98 el tipo subyacente de una enumeración sigue las mismas reglas que en C, que no son precisamente sencillas, y en muchos casos depende del compilador (básicamente el tipo subyacente es un entero lo bastante grande para representar todos los valores de la enumeración, pero no más grande de int, a no ser que sea necesario).
Al programador casual le puede parecer que el tipo subyacente de una enumeración es irrelevante, pero es importante para asegurar la compatibilidad binaria si forma parte de una estructura, si se guarda en un fichero o si se envía por la red. Hasta ahora, si esta cuestión era importante, solía ser más prudente declarar la variable de tipo entero y usar la enumeración como una simple colección de constantes:

enum Numero{ CERO, UNO, DOS, TRES };
struct X
{
   //...
   char numero; //enum Numero
   //...
};

Otros, en ocasiones, preferían forzar al compilador a seleccionar un tipo subyacente particular, escogiendo cuidadosamente el rango de valores:

enum Numero { CERO, UNO, DOS, TRES, NUMERO_INTMAX = 0x7FFFFFFF };
struct X
{
   //...
   Numero numero; //entero de 32 bits, probablemente
   //...
};

Pero esta solución no resulta muy legible ni portable, y el conjunto de tipos enteros posibles es bastente limitado

En C++0x se puede indicar el tipo subyacente de una enumeración simplemente indicándolo en la declaración:

enum class Numero : char { CERO, UNO, DOS, TRES };
struct X
{
   //...
   Numero numero; //equivale a char
   //...
};

Además, esto puede combinarse con la enumeración con ámbito.

Enumeraciones opacas

Casi todos hemos intentado alguna vez hacer una declaración incompleta de una enumeración:

enum fruta; //error
//...
enum fruta {pera, naranja, manzana};

Al fin y al cabo, puede hacerse con clases, estructuras y uniones, por qué no con enumeraciones. Pero resulta que no es válido ni en C ni en C++. La razón es que las declaraciones incompletas se inventaron para definir estructuras con dependencias circulares (clase A incluye una referencia a B, B incluye una referencia a C y C incluye una referencia a A), y las enumeraciones no dependen de nada.
Resulta, que en C++ es frecuente usar las declaraciones incompletas para evitar hacer includes innecesarios. Y para esto resultaría útil la declaración previa de enumeraciones.

En C++0x se incluyen las llamadas declaraciones opacas de enumeraciones. Al contrario que con las declaraciones incompletas de clases, estructuras y uniones, una enumeración así declarada no es un tipo incompleto, es decir, se pueden declarar variables, y no solo punteros y referencias. Lo único que le falta a una declaración opaca es la lista de enumeradores. Y puesto que el compilador debe saber el tipo subyacente de la enumeración para poder definir variables, éste se debe indicar expresamente.

Fichero frutas.h:

enum fruta : char
{ pera, naranja, manzana };

Fichero comida.h:

//no hay que incluir frutas.h solo por la enum.
enum fruta : char;
 
class Comida
{
public:
    Comida(fruta f)
        :m_fruta(f)
    {}
private:
    fruta m_fruta;
};

La excepción a esta regla es si se usa una enumeración con ámbito, en este caso, si no se indica el tipo subyacente, se asume int.

Deja un comentario