Declarando uma enumeração dentro de uma classe

150

No seguinte trecho de código, a Colorenumeração é declarada na Carclasse para limitar o escopo da enumeração e tentar não "poluir" o espaço para nome 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) Essa é uma boa maneira de limitar o escopo da Colorenumeração? Ou devo declará-lo fora da Carclasse, mas possivelmente dentro de seu próprio espaço para nome ou estrutura? Acabei de encontrar este artigo hoje, que defende o último e discute alguns pontos interessantes sobre enumerações: http://gamesfromwithin.com/stupid-c-tricks-2-better-enums .

(2) Neste exemplo, ao trabalhar na classe, é melhor codificar a enumeração como Car::Color, ou seria Colorsuficiente? (Presumo que o primeiro seja melhor, apenas no caso de outro Colorenum declarado no espaço de nomes global. Dessa forma, pelo menos, somos explícitos sobre o enum a que estamos nos referindo.)

bporter
fonte

Respostas:

85
  1. Se Coloré algo específico apenas para Cars, é assim que você limita seu escopo. Se você tiver outra Colorenumeração usada por outras classes, poderá torná-la global (ou pelo menos externa Car).

  2. Não faz diferença. Se houver um global, o local ainda será usado, pois está mais próximo do escopo atual. Observe que, se você definir essas funções fora da definição de classe, precisará especificar explicitamente Car::Colorna interface da função.

Peter Alexander
fonte
12
2. Sim e não. Car::Color getColor()mas void Car::setColor(Color c)porque setColorjá temos o especificador.
Matthieu M. 24/03
84

Atualmente - usando C ++ 11 - você pode usar a classe enum para isso:

enum class Color { RED, BLUE, WHITE };

AFAII isso faz exatamente o que você deseja.

Andreas Florath
fonte
2
Infelizmente, ele não permite funções-membro: stackoverflow.com/a/53284026/7395227
Andreas
66

Eu prefiro seguir a abordagem (código abaixo). Ele resolve o problema da "poluição do espaço para nome", mas também é muito mais seguro (você não pode atribuir e nem comparar duas enumerações diferentes, ou a sua enumeração com outros tipos internos, 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

Crio macro para facilitar o 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))
             )

Algumas referências:

  1. Herb Sutter, Jum Hyslop, C / C ++ Users Journal, 22 (5), maio de 2004
  2. Herb Sutter, David E. Miller, Enums fortemente tipados em Bjarne Stroustrup (revisão 3), julho de 2007
Sergey Teplyakov
fonte
Eu gosto disso. Também força o enum a ser instanciado com um valor válido. Eu acho que um operador de atribuição e um construtor de cópias seriam úteis. Também t_ deve ser privado. As macros que eu posso prescindir.
Jmucchiello
Eu também gosto disso. Obrigado pelas referências.
Anio
1
Você disse: "também é muito mais seguro (você não pode atribuir e nem comparar duas enumerações diferentes ..." . Por que você acha que é um bom recurso? Eu acho que if(c2 == Color::Red )é razoável e preciso compilar, mas no seu exemplo ele . não mesmo argumento para atribuição também!
Nawaz
3
@Nawaz c2é de outro tipo ( Color2), então por que você acha que as c2 == Color::Redtarefas devem ser compiladas? E se Color::Redfor 1 e Color2::Redfor 2? Deve Color::Red == Color2::Redavaliar para trueou false? Se você misturar enumeradores que não sejam tipicamente seguros, terá um mau momento.
Victor K
2
Por que não digite t_; privado?
Zingam
7

Em geral, eu sempre coloco minhas enumerações em a struct. Eu já vi várias diretrizes, incluindo "prefixo".

enum Color
{
  Clr_Red,
  Clr_Yellow,
  Clr_Blue,
};

Sempre pensei que isso parecia mais Cdiretrizes do que diretrizes C++(por uma por causa da abreviação e também por causa dos espaços para nome em C++).

Portanto, para limitar o escopo, agora temos duas alternativas:

  • namespaces
  • estruturas / classes

Pessoalmente, costumo usar a structporque ele pode ser usado como parâmetros para a programação de modelos, enquanto um espaço para nome não pode ser manipulado.

Exemplos de manipulação incluem:

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

que retorna o número de elementos de enum dentro da estrutura T:)

Matthieu M.
fonte
3

Se você estiver criando uma biblioteca de códigos, eu usaria um espaço para nome. No entanto, você ainda pode ter apenas uma enumeração de cores dentro desse espaço para nome. Se você precisar de uma enumeração que possa usar um nome comum, mas que possa ter constantes diferentes para classes diferentes, use sua abordagem.

Harvey
fonte