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?
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?
Respostas:
C ++ possui dois tipos de enum
:
enum class
esenum
sAqui 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 class
es - nomes de enumeradores são locais para a enumeração e seus valores não se convertem implicitamente em outros tipos (como outro enum
ou int
)
Plain enum
s - 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;
}
enum class
es deve ser preferido, pois causa menos surpresas que podem levar a erros.
A
com state e criar um enum class State { online, offline };
como filho da classe A
, gostaria de fazer state == online
verificações dentro de em A
vez de state == State::online
... isso é possível?
enum class
era eliminá-la.
Color color = Color::red
.
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.
Do FAQ C ++ 11 de Bjarne Stroustrup :
As
enum class
es ("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
enum
nã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" enum
deve 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 char
ou um tipo inteiro assinado / não assinado.
Esta é uma descrição ampla do que um enum
tipo subjacente deve ser; portanto, cada compilador tomará suas próprias decisões sobre o tipo subjacente do clássico enum
e, à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_FRUITS
valores em um tipo de 8 bits não assinado ... mas não há garantia: o compilador pode escolher unsigned char
ou int
ou 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_FRUITS
tenha 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 enum
e 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 class
uma escolha melhor ?:
int
.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 red
está se referindo ao tipo Color1
ou Color2
como 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??)
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.
enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }
é comparável a classe Enum: enum class Color1 { RED, GREEN, BLUE }
. O acesso é semelhante: COLOR1_RED
vs 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.
enum Color1
, dos quais um compilador não pode capturar, pois provavelmente ainda seria um nome 'válido'. Se eu escrever RED
, GREEN
e assim por diante, usando uma classe enum, isso não pode ser resolvido enum Banana
porque requer que você especifique Color1::RED
para 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 class
pode frequentemente ser muito benéfico.
Enumerações são usadas para representar um conjunto de valores inteiros.
A class
palavra-chave após enum
especifica que a enumeração é fortemente digitada e seus enumeradores têm escopo definido. Dessa forma, as enum
classes 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
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;
}
Vale ressaltar, além dessas outras respostas, que o C ++ 20 resolve um dos problemas que enum class
tem: 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 à enum
variaçã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 enum
para 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
.
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
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
};