Para que serve um enum
Uma struct agrupa vários valores relacionados em um único objeto. Um enum resolve um problema diferente: ele dá nomes a um conjunto pequeno e fixo de opções. Em vez de lembrar que 0 significa "vermelho", 1 significa "verde" e 2 significa "azul", você escreve Color::Red e o compilador te mantém honesto.
Recorrer a um enum sempre que uma variável só pode assumir um de um punhado de estados nomeados — a cor de um semáforo, o naipe de uma carta, um status de conexão — torna o código autoexplicativo e permite que o compilador pegue erros de digitação e casos faltantes que inteiros puros nunca pegariam.
Declarando um enum
O C++ moderno tem duas variantes. Comece pela que você deve usar quase sempre: o enum class com escopo. Você lista os nomes, e cada enumerador é acessado pelo nome do enum com :::
Repare que você escreve Color::Green, nunca um Green solto. Os valores em si são apenas rótulos: você os compara, atribui e passa adiante, mas raramente se importa com o número subjacente. Por padrão Red é 0, Green é 1 e Blue é 2, contando a partir de zero.
enum comum vs enum class
O enum mais antigo e sem escopo (sem a palavra-chave class) despeja seus nomes diretamente no escopo ao redor e se converte para int sozinho. Isso parece conveniente, mas causa dois problemas reais:
enum Color { Red, Green, Blue };
enum Fruit { Apple, Banana, Red }; // error: 'Red' already declared
enum Status { Active, Inactive };
int x = Active; // compiles silently - is this what you meant?
if (Active == Banana) { // compares unrelated enums via int - allowed!
}
Como os enumeradores comuns são nomes globais, dois enums podem colidir só por compartilharem um rótulo. E como eles decaem para int, o compilador compara tranquilamente valores de enums totalmente sem relação. Um enum class com escopo corrige as duas coisas: os nomes vivem dentro do tipo, e o tipo não vai se transformar silenciosamente em um int:
A regra prática: use enum class por padrão. Só recorra a um enum comum quando você quiser especificamente a conversão implícita para int, como nas antigas constantes de flags no estilo C.
Valores personalizados e o tipo subjacente
Você pode atribuir números explícitos aos enumeradores. Os que você deixar de fora continuam contando a partir do anterior, o que é prático para coisas como códigos de status HTTP ou flags de bits:
Todo enum é respaldado por um tipo inteiro — int por padrão. Você pode fixá-lo em um tipo menor quando se importa com o tamanho, por exemplo ao armazenar muitos enums em uma estrutura compacta ou ao corresponder a um formato de transmissão:
Escolher o tipo subjacente também garante a faixa em que seus valores devem caber: um enum uint8_t não pode conter um valor acima de 255.
Convertendo entre enum e int
Um enum class com escopo nunca se converte implicitamente, e esse é justamente o objetivo. Quando você realmente precisa do número — para imprimi-lo, indexar um array ou lê-lo de um arquivo — recorra ao static_cast. Ir de enum para int é sempre seguro:
Converter um int de volta para um enum é a direção perigosa. O cast não verifica que o número corresponda a um enumerador real: ele vai te entregar um valor que tecnicamente está fora do conjunto de nomes do enum:
Suit s = static_cast<Suit>(2); // fine - that's Clubs
Suit bad = static_cast<Suit>(99); // compiles, but 99 is not a valid Suit
// using `bad` in a switch or as an array index is a lurking bug
Se o inteiro vem da entrada do usuário ou de um arquivo, valide você mesmo a faixa antes de fazer o cast, ou você criará um valor que nenhum case jamais trata — uma fonte sutil de comportamento indefinido mais adiante.
Usando enums com switch
Como um enum é "um de um conjunto fixo", ele combina perfeitamente com um switch. Quando você cobre todos os enumeradores, muitos compiladores vão avisar se mais tarde você adicionar um valor novo e esquecer de tratá-lo: uma segurança de graça que você não tem com inteiros puros:
Uma pegadinha: não há uma forma nativa de imprimir o nome de um enumerador. cout << TrafficLight::Red não vai compilar para um enum com escopo, e mesmo para um enum comum ele imprime o número, não "Red". Um pequeno switch ou uma tabela de consulta como a acima é a forma usual de transformar um enum em uma string legível para humanos.
Próximo: Exceções
Enums e structs permitem modelar como seus dados se parecem. Mas programas reais também precisam lidar com coisas dando errado — um arquivo que não abre, um número que não dá para parsear, um valor fora da faixa. C++ trata esses caminhos de falha com exceções, e esse é o tema da próxima página.
Perguntas frequentes
Qual é a diferença entre enum e enum class em C++?
Um enum comum vaza seus nomes para o escopo ao redor e se converte implicitamente para int, o que causa conflitos de nomes e comparações acidentais. Um enum class com escopo mantém seus nomes dentro do enum (Color::Red) e se recusa a converter para int sem um cast explícito. Prefira enum class no C++ moderno: ele é seguro quanto a tipos e evita as pegadinhas clássicas.
Como converter um enum de C++ para um int?
Um enum comum se converte implicitamente, então int n = Red; simplesmente funciona. Um enum class com escopo exige um cast explícito: int n = static_cast<int>(Color::Red);. Para fazer o caminho inverso, faça o cast do int de volta: Color c = static_cast<Color>(2);, mas cuidado: em tempo de execução não há verificação de que o valor seja um enumerador válido.
Em que valor o primeiro enum de C++ começa?
Por padrão o primeiro enumerador é 0, e cada um seguinte vale um a mais que o anterior. Então em enum class Level { Low, Mid, High };, Low é 0, Mid é 1 e High é 2. Você pode atribuir valores explícitos a qualquer um deles para sobrescrever esse comportamento.