namespaces para tipos de enum - práticas recomendadas

102

Freqüentemente, são necessários vários tipos enumerados juntos. Às vezes, há um conflito de nomes. Duas soluções vêm à mente: use um namespace ou nomes de elemento enum 'maiores'. Ainda assim, a solução de namespace tem duas implementações possíveis: uma classe fictícia com enum aninhado ou um namespace completo.

Estou procurando os prós e os contras das três abordagens.

Exemplo:

// 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
fonte
19
Em primeiro lugar, eu usaria Color :: Red, Feeling: Angry, etc
abatishchev
boa pergunta, usei o método de namespace ....;)
MiniScalope
18
o prefixo "c" em tudo prejudica a legibilidade.
Usuário
8
@User: ora, obrigado por abrir a discussão húngara :)
xtofl
5
Observe que você não precisa nomear o enum como em enum e {...}, enums podem ser anônimos, ou seja enum {...}, o que faz muito mais sentido quando agrupado em namespace ou classe.
kralyk

Respostas:

73

Resposta original C ++ 03:

A vantagem de a namespace(sobre a class) é que você pode usar usingdeclarações quando quiser.

O problema de usar um namespaceé que os namespaces podem ser expandidos em outras partes do código. Em um grande projeto, você não teria a garantia de que duas enums distintas não pensam que são chamadaseFeelings

Para um código de aparência mais simples, eu uso um struct, já que você provavelmente deseja que o conteúdo seja público.

Se você está fazendo qualquer uma dessas práticas, está à frente da curva e provavelmente não precisa examinar isso mais profundamente.

Conselho mais recente, C ++ 11:

Se você estiver usando C ++ 11 ou posterior, enum classo escopo dos valores enum será implicitamente dentro do nome do enum.

Com enum classvocê perderá conversões implícitas e comparações com tipos inteiros, mas na prática isso pode ajudá-lo a descobrir códigos ambíguos ou com erros.

Drew Dormann
fonte
4
Eu concordo com a ideia-estrutura. E obrigado pelo elogio :)
xtofl
3
+1 Eu não conseguia me lembrar da sintaxe "classe enum" do C ++ 11. Sem esse recurso, enums são incompletos.
Grault
É uma opção de usar "using" para o escopo implícito da "classe enum". por exemplo, Will adicionando 'using Color :: e;' codificar permite o uso de 'cRed' e sabe que deve ser Color :: e :: cRed?
JVApen
20

Para sua informação, em C ++ 0x há uma nova sintaxe para casos como o que você mencionou (consulte a página wiki de C ++ 0x )

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

Eu hibridizei as respostas anteriores para algo assim: (EDITAR: Isso só é útil para pré-C ++ 11. Se você estiver usando C ++ 11, use enum class)

Eu tenho um grande arquivo de cabeçalho que contém todos os meus enums do projeto, porque esses enums são compartilhados entre as classes de trabalho e não faz sentido colocar os enums nas próprias classes de trabalho.

O structevita o açúcar sintático public: e o typedefpermite que você realmente declare variáveis ​​dessas enums dentro de outras classes de trabalho.

Não acho que usar um namespace ajude em nada. Talvez seja porque sou um programador C #, e você precisa usar o nome do tipo enum ao referir-se aos valores, então estou acostumado a isso.

    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
fonte
9

Eu definitivamente evitaria usar uma classe para isso; use um namespace em vez disso. A questão se resume a usar um namespace ou ids exclusivos para os valores de enum. Pessoalmente, eu usaria um namespace para que meus ids fossem mais curtos e, com sorte, mais autoexplicativos. Então, o código do aplicativo poderia usar uma diretiva 'using namespace' e tornar tudo mais legível.

Do seu exemplo acima:

using namespace Colors;

void setPenColor( const e c ) {
    switch (c) {
        default: assert(false);
        break; case cRed: //...
        break; case cBlue: //...
        //...
    }
}
Charles Anderson
fonte
Você poderia dar uma dica de por que você prefere um namespace a uma classe?
xtofl
@xtofl: você não pode escrever 'usando a classe Cores'
MSalters
2
@MSalters: Você também não pode escrever Colors someColor = Red;, porque o namespace não constitui um tipo. Você teria que escrever em Colors::e someColor = Red;vez disso, o que é bastante contra-intuitivo.
SasQ
@SasQ Você não teria que usar Colors::e someColormesmo com um struct/classse quisesse usá-lo em uma switchdeclaração? Se você usar um anônimo enum, o switch não será capaz de avaliar a struct.
Enigma de Macbeth
1
Desculpe, mas const e cparece dificilmente legível para mim :-) Não faça isso. Usar um namespace, entretanto, está bem.
dhaumann
7

Uma diferença entre usar uma classe ou um namespace é que a classe não pode ser reaberta como um namespace pode. Isso evita a possibilidade de que o namespace possa ser abusado no futuro, mas também existe o problema de que você também não pode adicionar ao conjunto de enumerações.

Um possível benefício de usar uma classe é que eles podem ser usados ​​como argumentos de tipo de modelo, o que não é o caso para namespaces:

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

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

Pessoalmente, não sou um fã de usar e prefiro os nomes totalmente qualificados, então não vejo isso como uma vantagem para os namespaces. No entanto, esta provavelmente não é a decisão mais importante que você fará em seu projeto!

Richard Corden
fonte
Não reabrir classes também é uma desvantagem potencial. A lista de cores também não é finita.
MSalters em
1
Acho que não renovar uma aula é uma vantagem potencial. Se eu quiser mais cores, recompilaria a classe com mais cores. Se eu não puder fazer isso (digamos que não tenho o código), então não quero mexer nisso de forma alguma.
Thomas Eding
@MSalters: A possibilidade de reabrir uma classe não é apenas uma desvantagem, mas também uma ferramenta de segurança. Porque quando fosse possível reabrir uma classe e adicionar alguns valores ao enum, poderia quebrar outro código de biblioteca que já depende desse enum e conhece apenas o antigo conjunto de valores. Ele então aceitaria alegremente esses novos valores, mas interromperia o tempo de execução por não saber o que fazer com eles. Lembre-se do princípio aberto-fechado: A classe deve ser fechada para modificação , mas aberta para extensão . Ao estender, quero dizer não adicionar ao código existente, mas envolvê-lo com o novo código (por exemplo, derivação).
SasQ
Portanto, quando você quiser estender o enum, deverá torná-lo um novo tipo derivado do primeiro (se fosse possível em C ++ ...; /). Então, ele poderia ser usado com segurança pelo novo código que entende esses novos valores, mas apenas os valores antigos seriam aceitos (convertendo-os para baixo) pelo código antigo. Eles não devem aceitar nenhum desses novos valores como sendo do tipo errado (o estendido). Apenas os valores antigos que eles entendem são aceitos como sendo do tipo correto (base) (e acidentalmente também sendo do novo tipo, de modo que também poderiam ser aceitos pelo novo código).
SasQ
7

A vantagem de usar uma classe é que você pode construir uma classe completa em cima dela.

#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 mostra o exemplo acima, usando uma classe, você pode:

  1. proíbe (infelizmente, não em tempo de compilação) C ++ de permitir uma conversão de valor inválido,
  2. definir um padrão (diferente de zero) para enums recém-criados,
  3. adicione outros métodos, como para retornar uma representação de string de uma escolha.

Apenas observe que você precisa declarar operator enum_type()para que C ++ saiba como converter sua classe em enum subjacente. Caso contrário, você não poderá passar o tipo para uma switchinstrução.

Michał Górny
fonte
Esta solução está de alguma forma relacionada ao que é mostrado aqui ?: en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Type_Safe_Enum Estou pensando em como torná-lo um modelo que eu não tenha que reescrever esse padrão todas as vezes Eu preciso usar isso.
SasQ
@SasQ: parece semelhante, sim. Provavelmente é a mesma ideia. No entanto, não tenho certeza se um modelo é benéfico, a menos que você esteja adicionando muitos métodos 'comuns' nele.
Michał Górny
1. Não é verdade. Você pode fazer com que o tempo de compilação verifique se o enum é válido, via const_expr ou via construtor privado para um int.
xryl669
5

Como os enums têm como escopo seu escopo delimitador, provavelmente é melhor envolvê-los em algo para evitar poluir o namespace global e ajudar a evitar colisões de nomes. Eu prefiro um namespace à classe simplesmente porque namespaceparece um saco de contenção, enquanto classparece um objeto robusto (cf. o debate structvs. class). Um possível benefício de um namespace é que ele pode ser estendido posteriormente - útil se você estiver lidando com código de terceiros que não pode ser modificado.

Isso tudo é discutível, é claro, quando obtemos classes enum com C ++ 0x.

Michael Kristofik
fonte
classes enum ... precisa pesquisar isso!
xtofl
3

Eu também tendo a encerrar minhas enums nas aulas.

Conforme assinalado por Richard Corden, o benefício de uma classe é que ela é um tipo no sentido de c ++ e, portanto, você pode usá-la com modelos.

Eu tenho a classe toolbox :: Enum especial para minhas necessidades, que me especializo para todos os modelos que fornecem funções básicas (principalmente: mapear um valor de enum para um std :: string para que as E / S sejam mais fáceis de ler).

Meu pequeno modelo também tem o benefício adicional de realmente verificar os valores permitidos. O compilador é meio negligente em verificar se o valor realmente está no enum:

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

Sempre me incomodou que o compilador não pegasse isso, já que você ficou com um valor enum que não tem sentido (e que você não esperaria).

Similarmente:

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

Mais uma vez, main retornará um erro.

O problema é que o compilador irá ajustar o enum na menor representação disponível (aqui precisamos de 2 bits) e que tudo que se encaixar nesta representação é considerado um valor válido.

Há também o problema de que às vezes você prefere ter um loop nos valores possíveis em vez de um switch, para não ter que modificar todos os switches cada vez que adiciona um valor ao enum.

Em suma, meu pequeno ajudante realmente facilita as coisas para meus enums (é claro, ele adiciona alguma sobrecarga) e só é possível porque aninho cada enum em sua própria estrutura :)

Matthieu M.
fonte
4
Interessante. Você se importa em compartilhar a definição de sua classe Enum?
momeara