Declarando una enumeración dentro de una clase

150

En el siguiente fragmento de código, la Colorenumeración se declara dentro de la Carclase para limitar el alcance de la enumeración y tratar de no "contaminar" el espacio de nombres global.

class Car
{
public:

   enum Color
   {
      RED,
      BLUE,
      WHITE
   };

   void SetColor( Car::Color color )
   {
      _color = color;
   }

   Car::Color GetColor() const
   {
      return _color;
   }

private:

   Car::Color _color;

};

(1) ¿Es esta una buena forma de limitar el alcance de la Colorenumeración? ¿O debería declararlo fuera de la Carclase, pero posiblemente dentro de su propio espacio de nombres o estructura? Acabo de encontrar este artículo hoy, que defiende este último y discute algunos puntos interesantes sobre las enumeraciones: http://gamesfromwithin.com/stupid-c-tricks-2-better-enums .

(2) En este ejemplo, cuando se trabaja dentro de la clase, ¿es mejor codificar la enumeración como Car::Color, o sería Colorsuficiente? (Supongo que lo primero es mejor, en caso de que haya otra Colorenumeración declarada en el espacio de nombres global. De esa manera, al menos, somos explícitos sobre la enumeración a la que nos referimos).

bporter
fuente

Respuestas:

85
  1. Si Colores algo específico para solo Cars, esa es la forma en que limitaría su alcance. Si va a tener otra Colorenumeración que usan otras clases, entonces también podría hacerlo global (o al menos fuera Car).

  2. No hace ninguna diferencia. Si hay uno global, el local todavía se usa de todos modos, ya que está más cerca del alcance actual. Tenga en cuenta que si define esas funciones fuera de la definición de clase, deberá especificarlas explícitamente Car::Coloren la interfaz de la función.

Peter Alexander
fuente
12
2. Sí y no. Car::Color getColor()pero void Car::setColor(Color c)porque setColorya tenemos el especificador.
Matthieu M.
84

Hoy en día, usando C ++ 11, puede usar la clase enum para esto:

enum class Color { RED, BLUE, WHITE };

AFAII esto hace exactamente lo que quieres.

Andreas Florath
fuente
2
Lamentablemente, no permite las funciones de miembro: stackoverflow.com/a/53284026/7395227
Andreas
66

Prefiero seguir el enfoque (código a continuación). Resuelve el problema de la "contaminación del espacio de nombres", pero también es mucho más seguro (no puede asignar e incluso comparar dos enumeraciones diferentes, o su enumeración con cualquier otro tipo incorporado, etc.).

struct Color
{
    enum Type
    {
        Red, Green, Black
    };
    Type t_;
    Color(Type t) : t_(t) {}
    operator Type () const {return t_;}
private:
   //prevent automatic conversion for any other built-in types such as bool, int, etc
   template<typename T>
    operator T () const;
};

Uso:

Color c = Color::Red;
switch(c)
{
   case Color::Red:
     //некоторый код
   break;
}
Color2 c2 = Color2::Green;
c2 = c; //error
c2 = 3; //error
if (c2 == Color::Red ) {} //error
If (c2) {} error

Creo macro para facilitar el uso:

#define DEFINE_SIMPLE_ENUM(EnumName, seq) \
struct EnumName {\
   enum type \
   { \
      BOOST_PP_SEQ_FOR_EACH_I(DEFINE_SIMPLE_ENUM_VAL, EnumName, seq)\
   }; \
   type v; \
   EnumName(type v) : v(v) {} \
   operator type() const {return v;} \
private: \
    template<typename T> \
    operator T () const;};\

#define DEFINE_SIMPLE_ENUM_VAL(r, data, i, record) \
    BOOST_PP_TUPLE_ELEM(2, 0, record) = BOOST_PP_TUPLE_ELEM(2, 1, record),

Uso:

DEFINE_SIMPLE_ENUM(Color,
             ((Red, 1))
             ((Green, 3))
             )

Algunas referencias:

  1. Herb Sutter, Jum Hyslop, C / C ++ Users Journal, 22 (5), mayo de 2004
  2. Herb Sutter, David E. Miller, Bjarne Stroustrup Enumeraciones fuertemente tipadas (revisión 3), julio de 2007
Sergey Teplyakov
fuente
Me gusta esto. También obliga a la enumeración a instanciarse con un valor válido. Creo que sería útil un operador de asignación y un constructor de copia. También t_ debe ser privado. Las macros que puedo prescindir.
jmucchiello
A mí también me gusta esto. Gracias por las referencias.
anio 29/10/10
1
Usted dijo: "también es mucho más seguro (no puede asignar e incluso comparar dos enumeraciones diferentes ..." . ¿Por qué cree que es una buena característica? Creo que if(c2 == Color::Red )es razonable y debe compilar, pero en su ejemplo . no hace el mismo argumento a favor de asignación también!
Nawaz
3
@Nawaz c2es de otro tipo ( Color2), entonces, ¿por qué crees que las c2 == Color::Redtareas deberían compilarse? ¿Qué pasa si Color::Redes 1 y Color2::Redes 2? Debe Color::Red == Color2::Redevaluar a trueo false? Si mezclas enumeradores que no sean seguros para escribir, lo pasarás mal.
Victor K
2
¿Por qué no es Tipo t_; ¿privado?
Zingam
7

En general, siempre pongo mis enumeraciones en a struct. He visto varias pautas que incluyen "prefijos".

enum Color
{
  Clr_Red,
  Clr_Yellow,
  Clr_Blue,
};

Siempre pensé que esto se parecía más a Cpautas que a C++unas (por una abreviatura y también por los espacios de nombres C++).

Entonces, para limitar el alcance, ahora tenemos dos alternativas:

  • espacios de nombres
  • estructuras / clases

Personalmente, tiendo a usar un structporque se puede usar como parámetros para la programación de plantillas, mientras que un espacio de nombres no se puede manipular.

Los ejemplos de manipulación incluyen:

template <class T>
size_t number() { /**/ }

que devuelve el número de elementos de enum dentro de la estructura T:)

Matthieu M.
fuente
3

Si está creando una biblioteca de códigos, usaría el espacio de nombres. Sin embargo, solo puede tener una enumeración de color dentro de ese espacio de nombres. Si necesita una enumeración que podría usar un nombre común, pero podría tener diferentes constantes para diferentes clases, use su enfoque.

Harvey
fuente