espacios de nombres para tipos de enumeración: mejores prácticas

102

A menudo, se necesitan varios tipos enumerados juntos. A veces, uno tiene un choque de nombres. Se me ocurren dos soluciones a esto: use un espacio de nombres o use nombres de elementos de enumeración 'más grandes'. Aún así, la solución de espacio de nombres tiene dos posibles implementaciones: una clase ficticia con enumeración anidada o un espacio de nombres completo.

Estoy buscando pros y contras de los tres enfoques.

Ejemplo:

// oft seen hand-crafted name clash solution
enum eColors { cRed, cColorBlue, cGreen, cYellow, cColorsEnd };
enum eFeelings { cAngry, cFeelingBlue, cHappy, cFeelingsEnd };
void setPenColor( const eColors c ) {
    switch (c) {
        default: assert(false);
        break; case cRed: //...
        break; case cColorBlue: //...
        //...
    }
 }


// (ab)using a class as a namespace
class Colors { enum e { cRed, cBlue, cGreen, cYellow, cEnd }; };
class Feelings { enum e { cAngry, cBlue, cHappy, cEnd }; };
void setPenColor( const Colors::e c ) {
    switch (c) {
        default: assert(false);
        break; case Colors::cRed: //...
        break; case Colors::cBlue: //...
        //...
    }
 }


 // a real namespace?
 namespace Colors { enum e { cRed, cBlue, cGreen, cYellow, cEnd }; };
 namespace Feelings { enum e { cAngry, cBlue, cHappy, cEnd }; };
 void setPenColor( const Colors::e c ) {
    switch (c) {
        default: assert(false);
        break; case Colors::cRed: //...
        break; case Colors::cBlue: //...
        //...
    }
  }
xtofl
fuente
19
En primer lugar, usaría Color :: Red, Feeling: Angry, etc
abatishchev
buena pregunta, utilicé el método de espacio de nombres ....;)
MiniScalope
18
el prefijo "c" en todo daña la legibilidad.
Usuario
8
@User: por qué, gracias por abrir la discusión húngara :)
xtofl
5
Tenga en cuenta que no necesita nombrar la enumeración como en enum e {...}, las enumeraciones pueden ser anónimas, es decir enum {...}, lo que tiene mucho más sentido cuando se incluye en un espacio de nombres o una clase.
kralyk

Respuestas:

73

Respuesta original de C ++ 03:

El beneficio de a namespace(sobre a class) es que puede usar usingdeclaraciones cuando lo desee.

El problema con el uso de un namespacees que los espacios de nombres se pueden expandir en otras partes del código. En un proyecto grande, no se le garantizaría que dos enumeraciones distintas no crean que se llamaneFeelings

Para un código de apariencia más simple, uso un struct, ya que presumiblemente desea que el contenido sea público.

Si está realizando alguna de estas prácticas, está por delante de la curva y probablemente no necesite examinar esto más a fondo.

Más reciente, consejo de C ++ 11:

Si está utilizando C ++ 11 o posterior, enum classimplícitamente abarcará los valores de enumeración dentro del nombre de la enumeración.

Con enum classperderá conversiones implícitas y comparaciones con tipos enteros, pero en la práctica eso puede ayudarlo a descubrir código ambiguo o con errores.

Drew Dormann
fuente
4
Estoy de acuerdo con la idea de estructura. Y gracias por el cumplido :)
xtofl
3
+1 No podía recordar la sintaxis de la "clase de enumeración" de C ++ 11. Sin esa característica, las enumeraciones están incompletas.
Grault
Es una opción para usar "using" para el alcance implícito de la "clase enum". por ejemplo, se agregará 'usando Color :: e;' codificar permitir el uso de 'cRed' y saber que debería ser Color :: e :: cRed?
JVApen
20

Para su información, en C ++ 0x hay una nueva sintaxis para casos como el que mencionó (consulte la página wiki de C ++ 0x )

enum class eColors { ... };
enum class eFeelings { ... };
jmihalicza
fuente
11

Hibridé las respuestas anteriores a algo como esto: (EDITAR: Esto solo es útil para versiones anteriores a C ++ 11. Si está usando C ++ 11, use enum class)

Tengo un archivo de encabezado grande que contiene todas las enumeraciones de mi proyecto, porque estas enumeraciones se comparten entre clases trabajadoras y no tiene sentido poner las enumeraciones en las clases trabajadoras.

El structevita el público: sintáctica azúcar, y la typedefdeja en realidad se declara variables de estas enumeraciones dentro de otras clases obreras.

No creo que el uso de un espacio de nombres ayude en absoluto. Tal vez esto se debe a que soy un programador de C #, y hay que tener para usar el nombre del tipo de enumeración cuando se refiere a los valores, así que estoy acostumbrado a ella.

    struct KeySource {
        typedef enum { 
            None, 
            Efuse, 
            Bbram
        } Type;
    };

    struct Checksum {
        typedef enum {
            None =0,
            MD5 = 1,
            SHA1 = 2,
            SHA2 = 3
        } Type;
    };

    struct Encryption {
        typedef enum {
            Undetermined,
            None,
            AES
        } Type;
    };

    struct File {
        typedef enum {
            Unknown = 0,
            MCS,
            MEM,
            BIN,
            HEX
        } Type;
    };

...

class Worker {
    File::Type fileType;
    void DoIt() {
       switch(fileType) {
       case File::MCS: ... ;
       case File::MEM: ... ;
       case File::HEX: ... ;
    }
}
Mark Lakata
fuente
9

Definitivamente evitaría usar una clase para esto; use un espacio de nombres en su lugar. La pregunta se reduce a si usar un espacio de nombres o usar identificadores únicos para los valores de enumeración. Personalmente, usaría un espacio de nombres para que mis identificadores pudieran ser más cortos y, con suerte, más autoexplicativos. Luego, el código de la aplicación podría usar una directiva de 'uso de espacio de nombres' y hacer que todo sea más legible.

De su ejemplo anterior:

using namespace Colors;

void setPenColor( const e c ) {
    switch (c) {
        default: assert(false);
        break; case cRed: //...
        break; case cBlue: //...
        //...
    }
}
Charles Anderson
fuente
¿Podría darnos una pista de por qué preferiría un espacio de nombres en lugar de una clase?
xtofl
@xtofl: no puede escribir 'usando colores de clase'
MSalters
2
@MSalters: Tampoco puede escribir Colors someColor = Red;, porque el espacio de nombres no constituye un tipo. En su Colors::e someColor = Red;lugar, tendría que escribir , lo cual es bastante contrario a la intuición.
SasQ
@SasQ ¿No tendrías que usar Colors::e someColorincluso con a struct/classsi quisieras usarlo en una switchdeclaración? Si usa un anónimo enum, el conmutador no podrá evaluar un struct.
Macbeth's Enigma
1
Lo siento, pero me const e cparece difícil de leer :-) No hagas eso. Sin embargo, usar un espacio de nombres está bien.
dhaumann
7

Una diferencia entre usar una clase o un espacio de nombres es que la clase no se puede volver a abrir como un espacio de nombres. Esto evita la posibilidad de que se abuse del espacio de nombres en el futuro, pero también existe el problema de que tampoco se puede agregar al conjunto de enumeraciones.

Un posible beneficio de usar una clase es que se pueden usar como argumentos de tipo de plantilla, lo que no es el caso de los espacios de nombres:

class Colors {
public:
  enum TYPE {
    Red,
    Green,
    Blue
  };
};

template <typename T> void foo (T t) {
  typedef typename T::TYPE EnumType;
  // ...
}

Personalmente, no soy un fanático del uso y prefiero los nombres completamente calificados, por lo que realmente no veo eso como una ventaja para los espacios de nombres. Sin embargo, probablemente esta no sea la decisión más importante que tomará en su proyecto.

Richard Corden
fuente
No reabrir clases también es una desventaja potencial. La lista de colores tampoco es finita.
MSalters
1
Creo que no recuperar una clase es una ventaja potencial. Si quiero más colores, simplemente recompilaría la clase con más colores. Si no puedo hacer esto (digamos que no tengo el código), entonces no quiero tocarlo en ningún caso.
Thomas Eding
@MSalters: No hay posibilidad de reabrir una clase no solo no es una desventaja, sino también una herramienta de seguridad. Porque cuando sería posible volver a abrir una clase y agregar algunos valores a la enumeración, podría romper otro código de biblioteca que ya depende de esa enumeración y solo conoce el antiguo conjunto de valores. Entonces aceptaría felizmente esos nuevos valores, pero rompería en tiempo de ejecución por no saber qué hacer con ellos. Recuerde el principio abierto-cerrado: la clase debe estar cerrada para modificaciones , pero abierta para ampliar . Al extender me refiero a no agregar al código existente, sino a envolverlo con el nuevo código (por ejemplo, derivar).
SasQ
Entonces, cuando desee extender la enumeración, debe convertirla en un nuevo tipo derivado del primero (si solo fuera fácilmente posible en C ++ ...; /). Entonces podría ser utilizado de forma segura por el nuevo código que comprende esos nuevos valores, pero solo los valores antiguos serían aceptados (convirtiéndolos hacia abajo) por el código antiguo. No deberían aceptar ninguno de esos nuevos valores como del tipo incorrecto (el extendido). Solo los valores antiguos que entienden se aceptan como del tipo correcto (base) (y accidentalmente también del nuevo tipo, por lo que también podría ser aceptado por el nuevo código).
SasQ
7

La ventaja de usar una clase es que puede construir una clase completa sobre ella.

#include <cassert>

class Color
{
public:
    typedef enum
    {
        Red,
        Blue,
        Green,
        Yellow
    } enum_type;

private:
    enum_type _val;

public:
    Color(enum_type val = Blue)
        : _val(val)
    {
        assert(val <= Yellow);
    }

    operator enum_type() const
    {
        return _val;
    }
};

void SetPenColor(const Color c)
{
    switch (c)
    {
        case Color::Red:
            // ...
            break;
    }
}

Como muestra el ejemplo anterior, al usar una clase puede:

  1. prohíbe (lamentablemente, no en tiempo de compilación) que C ++ permita una conversión de un valor no válido,
  2. establecer un valor predeterminado (distinto de cero) para enumeraciones recién creadas,
  3. agregue más métodos, como para devolver una representación de cadena de una elección.

Solo tenga en cuenta que debe declarar operator enum_type()para que C ++ sepa cómo convertir su clase en una enumeración subyacente. De lo contrario, no podrá pasar el tipo a una switchdeclaración.

Michał Górny
fuente
¿Esta solución está relacionada de alguna manera con lo que se muestra aquí ?: en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Type_Safe_Enum Estoy pensando en cómo convertirlo en una plantilla que no tenga que reescribir este patrón cada vez Necesito usarlo.
SasQ
@SasQ: parece similar, sí. Probablemente sea la misma idea. Sin embargo, no estoy seguro de si una plantilla es beneficiosa a menos que esté agregando muchos métodos "comunes" allí.
Michał Górny
1. No es realmente cierto. Puede hacer que el tiempo de compilación verifique que la enumeración sea válida, a través de const_expr o mediante un constructor privado para un int.
xryl669
5

Dado que las enumeraciones tienen un alcance en su alcance adjunto, probablemente sea mejor envolverlas en algo para evitar contaminar el espacio de nombres global y ayudar a evitar colisiones de nombres. Prefiero un espacio de nombres a una clase simplemente porque se namespacesiente como una bolsa de tenencia, mientras que se classsiente como un objeto robusto (cf. el debate structvs. classUn posible beneficio de un espacio de nombres es que se puede ampliar más adelante, lo que resulta útil si se trata de un código de terceros que no puede modificar.

Por supuesto, todo esto es discutible cuando obtenemos clases de enumeración con C ++ 0x.

Michael Kristofik
fuente
clases de enumeración ... necesito buscar eso!
xtofl
3

También tiendo a envolver mis enumeraciones en clases.

Como señaló Richard Corden, el beneficio de una clase es que es un tipo en el sentido de C ++ y, por lo tanto, puede usarlo con plantillas.

Tengo una caja de herramientas especial :: clase Enum para mis necesidades que me especializo para cada plantilla que proporciona funciones básicas (principalmente: mapear un valor de enumeración a un std :: string para que la E / S sea más fácil de leer).

Mi pequeña plantilla también tiene el beneficio adicional de verificar realmente los valores permitidos. El compilador es un poco laxo al verificar si el valor realmente está en la enumeración:

typedef enum { False: 0, True: 2 } boolean;
   // The classic enum you don't want to see around your code ;)

int main(int argc, char* argv[])
{
  boolean x = static_cast<boolean>(1);
  return (x == False || x == True) ? 0 : 1;
} // main

Siempre me molestó que el compilador no captara esto, ya que te queda un valor de enumeración que no tiene sentido (y que no esperas).

Similar:

typedef enum { Zero: 0, One: 1, Two: 2 } example;

int main(int argc, char* argv[])
{
  example y = static_cast<example>(3);
  return (y == Zero || y == One || y == Two) ? 0 : 1;
} // main

Una vez más, main devolverá un error.

El problema es que el compilador encajará la enumeración en la representación más pequeña disponible (aquí necesitamos 2 bits) y que todo lo que encaje en esta representación se considera un valor válido.

También existe el problema de que a veces prefiere tener un bucle en los valores posibles en lugar de un interruptor para que no tenga que modificar todos los interruptores cada vez que agrega un valor a la enumeración.

Con todo, mi pequeño ayudante realmente facilita las cosas para mis enumeraciones (por supuesto, agrega algo de sobrecarga) y solo es posible porque anido cada enumeración en su propia estructura :)

Matthieu M.
fuente
4
Interesante. ¿Te importaría compartir la definición de tu clase Enum?
momeara