Por que a classe enum é preferida à enum comum?

429

Ouvi algumas pessoas recomendando o uso de classes enum em C ++ por causa de sua segurança de tipo .

Mas o que isso realmente significa?

Oleksiy
fonte
57
Quando alguém afirma que alguma construção de programação é "má", está tentando desencorajá-lo a pensar por si mesmo.
Pete Becker
3
@ NicolBolas: Essa é mais uma pergunta retórica para fornecer uma resposta para perguntas frequentes (se essa é realmente a pergunta Frequenty é outra história).
David Rodríguez - dribeas
@ David, há uma discussão sobre se isso deve ser um FAQ ou não, que começa aqui . Entrada bem-vinda.
SBI
17
@PeteBecker Às vezes, eles estão apenas tentando protegê- lo de si mesmo.
Picc:
geeksforgeeks.org/... Este também é um bom lugar para entender enumvs enum class.
mr_azad 20/04

Respostas:

472

C ++ possui dois tipos de enum:

  1. enum classes
  2. Plain enums

Aqui estão alguns exemplos de como declará-los:

 enum class Color { red, green, blue }; // enum class
 enum Animal { dog, cat, bird, human }; // plain enum 

Qual é a diferença entre two?

  • enum classes - nomes de enumeradores são locais para a enumeração e seus valores não se convertem implicitamente em outros tipos (como outro enumou int)

  • Plain enums - onde os nomes dos enumeradores estão no mesmo escopo que a enum e seus valores implicitamente se convertem em números inteiros e outros tipos

Exemplo:

enum Color { red, green, blue };                    // plain enum 
enum Card { red_card, green_card, yellow_card };    // another plain enum 
enum class Animal { dog, deer, cat, bird, human };  // enum class
enum class Mammal { kangaroo, deer, human };        // another enum class

void fun() {

    // examples of bad use of plain enums:
    Color color = Color::red;
    Card card = Card::green_card;

    int num = color;    // no problem

    if (color == Card::red_card) // no problem (bad)
        cout << "bad" << endl;

    if (card == Color::green)   // no problem (bad)
        cout << "bad" << endl;

    // examples of good use of enum classes (safe)
    Animal a = Animal::deer;
    Mammal m = Mammal::deer;

    int num2 = a;   // error
    if (m == a)         // error (good)
        cout << "bad" << endl;

    if (a == Mammal::deer) // error (good)
        cout << "bad" << endl;

}

Conclusão:

enum classes deve ser preferido, pois causa menos surpresas que podem levar a erros.

Oleksiy
fonte
7
Bom exemplo ... existe uma maneira de combinar a segurança de tipo da versão da classe com a promoção de namespace da versão enum? Ou seja, se eu tiver uma classe Acom state e criar um enum class State { online, offline };como filho da classe A, gostaria de fazer state == onlineverificações dentro de em Avez de state == State::online... isso é possível?
marca de
31
Não. A promoção do namespace é uma coisa ruim e metade da justificativa enum classera eliminá-la.
Filhote de cachorro
10
Em C ++ 11, você pode usar enums explicitamente digitados também, como animal enum: int não assinado {cão, veados, gato, pássaro}
Blasius Secundus
3
@ Cat Plus Plus Entendo que @Oleksiy diz que é ruim. Minha pergunta não era se Oleksiy pensava que era ruim. Minha pergunta foi um pedido para detalhar o que há de ruim nisso. Especificamente, por que Oleksiy, por exemplo, considera ruim Color color = Color::red.
chux - Reinstala Monica
9
@ Cat Plus Plus Portanto, o exemplo ruim não ocorre até a if (color == Card::red_card)linha, quatro linhas depois do comentário (o que eu vejo agora se aplica à primeira metade do bloco.) 2 linhas do bloco dão exemplos ruins . As 3 primeiras linhas não são um problema. O "bloco inteiro é o motivo pelo qual as enums comuns são ruins" me jogou como eu pensei que você quisesse dizer que havia algo errado com elas também. Entendo agora, é apenas uma configuração. De qualquer forma, obrigado pelo feedback.
chux - Reinstala Monica
248

Do FAQ C ++ 11 de Bjarne Stroustrup :

As enum classes ("novas enumerações", "enumerações fortes") abordam três problemas com as enumerações C ++ tradicionais:

  • enumerações convencionais convertem implicitamente em int, causando erros quando alguém não deseja que uma enumeração atue como um número inteiro.
  • enums convencionais exportam seus enumeradores para o escopo circundante, causando conflitos de nome.
  • o tipo subjacente de um enumnão pode ser especificado, causando confusão, problemas de compatibilidade e impossibilitando a declaração avançada.

As novas enumerações são "classe de enumeração" porque combinam aspectos de enumerações tradicionais (valores de nomes) com aspectos de classes (membros com escopo definido e ausência de conversões).

Portanto, como mencionado por outros usuários, as "enumerações fortes" tornariam o código mais seguro.

O tipo subjacente de um "clássico" enumdeve ser um tipo inteiro grande o suficiente para caber em todos os valores do enum; isso geralmente é um int. Além disso, cada tipo enumerado deve ser compatível com charou um tipo inteiro assinado / não assinado.

Esta é uma descrição ampla do que um enumtipo subjacente deve ser; portanto, cada compilador tomará suas próprias decisões sobre o tipo subjacente do clássico enume, às vezes, o resultado pode ser surpreendente.

Por exemplo, eu já vi código assim várias vezes:

enum E_MY_FAVOURITE_FRUITS
{
    E_APPLE      = 0x01,
    E_WATERMELON = 0x02,
    E_COCONUT    = 0x04,
    E_STRAWBERRY = 0x08,
    E_CHERRY     = 0x10,
    E_PINEAPPLE  = 0x20,
    E_BANANA     = 0x40,
    E_MANGO      = 0x80,
    E_MY_FAVOURITE_FRUITS_FORCE8 = 0xFF // 'Force' 8bits, how can you tell?
};

No código acima, algum codificador ingênuo está pensando que o compilador armazenará os E_MY_FAVOURITE_FRUITSvalores em um tipo de 8 bits não assinado ... mas não há garantia: o compilador pode escolher unsigned charou intou short, qualquer um desses tipos é grande o suficiente para caber em todos os valores vistos no enum. Adicionar um campo E_MY_FAVOURITE_FRUITS_FORCE8é um fardo e não força o compilador a fazer qualquer tipo de escolha sobre o tipo subjacente do enum.

Se houver algum pedaço de código que dependa do tamanho do tipo e / ou suponha que E_MY_FAVOURITE_FRUITStenha alguma largura (por exemplo: rotinas de serialização), esse código poderá se comportar de maneiras estranhas, dependendo dos pensamentos do compilador.

E para piorar a situação, se algum colega de trabalho acrescentar de forma descuidada um novo valor ao nosso enum:

    E_DEVIL_FRUIT  = 0x100, // New fruit, with value greater than 8bits

O compilador não se queixa! Ele apenas redimensiona o tipo para caber em todos os valores de enum(assumindo que o compilador esteja usando o menor tipo possível, o que é uma suposição que não podemos fazer). Esta adição simples e descuidada aoenum sutileza poderia quebrar códigos relacionados.

Como o C ++ 11 é possível especificar o tipo subjacente para enume enum class(obrigado rdb ), esse problema foi resolvido de maneira ordenada:

enum class E_MY_FAVOURITE_FRUITS : unsigned char
{
    E_APPLE        = 0x01,
    E_WATERMELON   = 0x02,
    E_COCONUT      = 0x04,
    E_STRAWBERRY   = 0x08,
    E_CHERRY       = 0x10,
    E_PINEAPPLE    = 0x20,
    E_BANANA       = 0x40,
    E_MANGO        = 0x80,
    E_DEVIL_FRUIT  = 0x100, // Warning!: constant value truncated
};

Especificando o tipo subjacente, se um campo tiver uma expressão fora do intervalo desse tipo, o compilador reclamará em vez de alterar o tipo subjacente.

Eu acho que isso é uma boa melhoria de segurança.

Então, por que a classe enum é preferida à enum comum? , se podemos escolher o tipo subjacente para enumerações com escopo ( enum class) e sem escopo ( enum), o que mais faz enum classuma escolha melhor ?:

  • Eles não se convertem implicitamente em int.
  • Eles não poluem o espaço para nome circundante.
  • Eles podem ser declarados a frente.
PaperBirdMaster
fonte
1
Suponho que pode restringir o tipo de base enum dentro para enums regulares, bem como, desde que temos C ++ 11
Sagar Padhye
11
Desculpe, mas esta resposta está errada. "classe enum" não tem nada a ver com a capacidade de especificar o tipo. Esse é um recurso independente que existe tanto para enumerações regulares quanto para enumerações.
Rdb 06/06
14
Este é o problema: * As classes Enum são um novo recurso do C ++ 11. * As enumerações digitadas são um novo recurso do C ++ 11. Esses são dois novos recursos independentes não relacionados no C ++ 11. Você pode usar ambos, ou você pode usar um ou nenhum.
Rdb 6/05
2
Eu acho que Alex Allain fornece a explicação simples mais completa que eu já vi neste blog em [ cprogramming.com/c++11/… . A enum tradicional era boa para usar nomes em vez de valores inteiros e evitar o pré-processador #defines, que era uma coisa boa - acrescentava clareza. A classe enum remove o conceito de um valor numérico do enumerador e introduz o escopo e a digitação forte que aumentam (bem, podem aumentar :-) a correção do programa. Ele o aproxima um passo do pensamento orientado a objetos.
Jon Spencer
2
Como um aparte, é sempre divertido quando você está revisando o código e de repente One Piece acontece.
Justin Time - Restabelece Monica
47

A vantagem básica de usar a classe enum sobre enumerações normais é que você pode ter as mesmas variáveis ​​de enumeração para duas enumerações diferentes e ainda pode resolvê-las (que foi mencionada como tipo segura pelo OP)

Por exemplo:

enum class Color1 { red, green, blue };    //this will compile
enum class Color2 { red, green, blue };

enum Color1 { red, green, blue };    //this will not compile 
enum Color2 { red, green, blue };

Quanto às enumerações básicas, o compilador não será capaz de distinguir se redestá se referindo ao tipo Color1ou Color2como na declaração abaixo.

enum Color1 { red, green, blue };   
enum Color2 { red, green, blue };
int x = red;    //Compile time error(which red are you refering to??)
Saksham
fonte
1
@Oleksiy Ohh, eu não li sua pergunta corretamente. Considerar é um complemento para quem não sabia.
Saksham
está certo! Eu quase me esqueci sobre isso
Oleksiy
é claro, você escreveria enum { COLOR1_RED, COLOR1_GREE, COLOR1_BLUE }, evitando facilmente problemas de espaço para nome. O argumento do espaço para nome é um dos três mencionados aqui que eu não compro.
Jo #
2
@ Jo Então essa solução é uma solução desnecessária. Ajustes: enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }é comparável a classe Enum: enum class Color1 { RED, GREEN, BLUE }. O acesso é semelhante: COLOR1_REDvs Color1::RED, mas a versão Enum exige que você digite "COLOR1" em cada valor, o que oferece mais espaço para erros de digitação, o que evita o comportamento do espaço de nome de uma classe enum.
cdgraham
2
Por favor, use críticas construtivas . Quando digo mais espaço para erros de digitação, quero dizer quando você define originalmente os valores de enum Color1, dos quais um compilador não pode capturar, pois provavelmente ainda seria um nome 'válido'. Se eu escrever RED, GREENe assim por diante, usando uma classe enum, isso não pode ser resolvido enum Bananaporque requer que você especifique Color1::REDpara acessar o valor (o argumento do espaço para nome). Ainda há bons momentos para usar enum, mas o comportamento do espaço para nome de um enum classpode frequentemente ser muito benéfico.
cdgraham
20

Enumerações são usadas para representar um conjunto de valores inteiros.

A classpalavra-chave após enumespecifica que a enumeração é fortemente digitada e seus enumeradores têm escopo definido. Dessa forma, as enumclasses evitam o uso acidental de constantes.

Por exemplo:

enum class Animal{Dog, Cat, Tiger};
enum class Pets{Dog, Parrot};

Aqui não podemos misturar valores de animais e animais de estimação.

Animal a = Dog;       // Error: which DOG?    
Animal a = Pets::Dog  // Pets::Dog is not an Animal
Alok151290
fonte
7

As perguntas frequentes do C ++ 11 mencionam os pontos abaixo:

enumerações convencionais convertem implicitamente em int, causando erros quando alguém não deseja que uma enumeração atue como um número inteiro.

enum color
{
    Red,
    Green,
    Yellow
};

enum class NewColor
{
    Red_1,
    Green_1,
    Yellow_1
};

int main()
{
    //! Implicit conversion is possible
    int i = Red;

    //! Need enum class name followed by access specifier. Ex: NewColor::Red_1
    int j = Red_1; // error C2065: 'Red_1': undeclared identifier

    //! Implicit converison is not possible. Solution Ex: int k = (int)NewColor::Red_1;
    int k = NewColor::Red_1; // error C2440: 'initializing': cannot convert from 'NewColor' to 'int'

    return 0;
}

enums convencionais exportam seus enumeradores para o escopo circundante, causando conflitos de nome.

// Header.h

enum vehicle
{
    Car,
    Bus,
    Bike,
    Autorickshow
};

enum FourWheeler
{
    Car,        // error C2365: 'Car': redefinition; previous definition was 'enumerator'
    SmallBus
};

enum class Editor
{
    vim,
    eclipes,
    VisualStudio
};

enum class CppEditor
{
    eclipes,       // No error of redefinitions
    VisualStudio,  // No error of redefinitions
    QtCreator
};

O tipo subjacente de uma enumeração não pode ser especificado, causando confusão, problemas de compatibilidade e impossibilitando a declaração avançada.

// Header1.h
#include <iostream>

using namespace std;

enum class Port : unsigned char; // Forward declare

class MyClass
{
public:
    void PrintPort(enum class Port p);
};

void MyClass::PrintPort(enum class Port p)
{
    cout << (int)p << endl;
}

.

// Header.h
enum class Port : unsigned char // Declare enum type explicitly
{
    PORT_1 = 0x01,
    PORT_2 = 0x02,
    PORT_3 = 0x04
};

.

// Source.cpp
#include "Header1.h"
#include "Header.h"

using namespace std;
int main()
{
    MyClass m;
    m.PrintPort(Port::PORT_1);

    return 0;
}
Swapnil
fonte
O C ++ 11 também permite digitar enumerações "sem classe" . Os problemas de poluição do espaço de nome, etc., ainda existem. Dê uma olhada nas respostas relevantes que existiam muito tempo antes desta ..
user2864740 17/02
7
  1. não converte implicitamente em int
  2. pode escolher qual o tipo subjacente
  3. Espaço de nome ENUM para evitar a poluição
  4. Comparado com a classe normal, pode ser declarado adiante, mas não possui métodos
Qinsheng Zhang
fonte
2

Vale ressaltar, além dessas outras respostas, que o C ++ 20 resolve um dos problemas que enum classtem: verbosidade. Imaginando um hipotético enum class, Color.

void foo(Color c)
  switch (c) {
    case Color::Red: ...;
    case Color::Green: ...;
    case Color::Blue: ...;
    // etc
  }
}

Isso é detalhado comparado à enumvariação simples , onde os nomes estão no escopo global e, portanto, não precisam ser prefixados Color::.

No entanto, no C ++ 20, podemos usar using enumpara introduzir todos os nomes em uma enumeração no escopo atual, resolvendo o problema.

void foo(Color c)
  using enum Color;
  switch (c) {
    case Red: ...;
    case Green: ...;
    case Blue: ...;
    // etc
  }
}

Portanto, agora, não há razão para não usar enum class.

Tom VH
fonte
1

Como, como dito em outras respostas, a classe enum não é implicitamente convertível em int / bool, também ajuda a evitar códigos de bugs, como:

enum MyEnum {
  Value1,
  Value2,
};
...
if (var == Value1 || Value2) // Should be "var == Value2" no error/warning
Arnaud
fonte
2
Para concluir meu comentário anterior, observe que o gcc agora possui um aviso chamado -Wint-in-bool-context que capturará exatamente esse tipo de erro.
Arnaud
0

Uma coisa que não foi mencionada explicitamente - o recurso de escopo oferece a opção de ter o mesmo nome para um método de enumeração e classe. Por exemplo:

class Test
{
public:
   // these call ProcessCommand() internally
   void TakeSnapshot();
   void RestoreSnapshot();
private:
   enum class Command // wouldn't be possible without 'class'
   {
        TakeSnapshot,
        RestoreSnapshot
   };
   void ProcessCommand(Command cmd); // signal the other thread or whatever
};
Miro Kropacek
fonte