O que é enum em C++?

O que é enum em C++?

Um enum (abreviação de enumeração) em C++ é um tipo de dado especial que nos permite agrupar um conjunto de valores (normalmente uma sequência de inteiros) constantes, e que torna possível acessar esses valores usando os nomes do seus elementos (também chamados de enumeradores).

Há dois tipos principais de enums em C++, enums sem escopo e enums com escopo, e nas seções a seguir falarei deles com mais detalhes.

Unscoped enum – enum sem escopo

Os enums sem escopo são definidos com a seguinte sintaxe:

Exemplo 1 – Sintaxe dos enums sem escopo

// Exemplo #1 - O que é enum em C++
// Sintaxe dos enums sem escopo
//enum (nomeDoEnum) [: tipo inteiro dos valores do enum] {list de enumeradores};

#include <iostream>
#include <string>
using namespace std;

int main () {
    
    enum DificuldadeDoJogo {
    Facil, // Valor 0
    Medio, // Valor 1
    Dificil, // Valor 2
    Impossivel = 15 // Valor 15
    };
    
    DificuldadeDoJogo minhaDificuldade {Facil};
    if (minhaDificuldade == 0) {
        cout << "A minha dificuldade é " << minhaDificuldade << endl;
    }
}

A minha dificuldade é 0

Como se pode ver no exemplo #1, um enum sem escopo é definido com a palavra enum seguida do nome do enum que se deseja criar, ambos seguidos pelo tipo dos dados do enum (este valor é opcional, e eu o omiti no meu enum DificuldadeDoJogo – por padrão os valores de um enum são de um tipo inteiro determinado pelo compilador, veja mais informações na página sobre enums CppReference em português)

Cada enumerador (ou seja, cada elemento) de um enum possui um valor inteiro que pode ser atribuído pelo programa ou não; se os valores não forem atribuídos, o que acontece é o seguinte: o primeiro enumerador corresponde ao valor 0 (no exemplo #1, Facil corresponde ao valor 0), o segundo corresponde ao valor 1 (Medio, no nosso exemplo), e assim por diante. Contudo, é possível atribuir um valor a apenas alguns dos elementos de um enum, como eu fiz com o Impossivel, que corresponde ao valor 15.

Problemas dos enums sem escopo

Até o C++11, os enums só podiam ser do tipo sem escopo, e apesar deles serem bastante úteis, ele possuem alguns problemas que são evitados pelos enums com escopo.

O principal problema do enum sem escopo é a poluição do escopo que o contém. O que isso quer dizer? Quer dizer que os valores dos enumeradores que pertencem ao enum não podem usar os nomes que já foram utilizados em seu escopo circundante, e os nomes que forem usados pelo enum se tornarão indisponíveis para outros objetos do mesmo escopo. Ainda não está claro? Tudo bem, vejamos com um exemplo.

// Exemplo #2 - O que é enum em C++?
// Principal problema dos enums sem escopo em C++
// Poluição do escopo

#include <iostream>
#include <string>
using namespace std;

int main () {
    
    enum DificuldadeDoJogo {
    Facil, // Valor 0
    Medio, // Valor 1
    Dificil, // Valor 2
    Impossivel = 15 // Valor 15
    };
    
    DificuldadeDoJogo minhaDificuldade {Facil};
    string Medio { "Medium" }; // ERRO - redefinição de Medio
    if (minhaDificuldade == 0) {
        cout << "A minha dificuldade é " << minhaDificuldade << endl;
    }
}

Scoped enum – enum com escopo

Os enums com escopo são definidos de modo muito semelhantes ao enums sem escopo. A única diferença entre os dois é que nos enums com escopo a palavra class vem logo após enum e logo antes do nome do enum em questão. Veja no exemplo a seguir o mesmo enum DificuldadeDoJogo usado nos exemplos #1 e #2.

// Exemplo #3 - O que é enum em C++
// Sintaxe dos enums com escopo
//enum class (nomeDoEnum) [: tipo inteiro dos valores do enum] {list de enumeradores};

#include <iostream>
#include <string>
using namespace std;

int main () {
    
    enum class DificuldadeDoJogo { // Dif. #1
    Facil, // Valor 0
    Medio, // Valor 1
    Dificil, // Valor 2
    Impossivel = 15 // Valor 15
    };
    
    DificuldadeDoJogo minhaDificuldade {DificuldadeDoJogo::Facil}; // Dif. #2
    if (minhaDificuldade == DificuldadeDoJogo::Facil) { // Dif. #3
        cout << "A minha dificuldade é " << static_cast<int>(minhaDificuldade) << endl; // Dif. #4
    }
}

A minha dificuldade é 0.

No exemplo #3 acima, podemos ver na linha 11 a primeira diferença entre os dois tipos de enum – com e sem escopo – é a presença da palavra class na definição dos enums com escopo. Essa palavra nos indica que o enum se comporta, de alguma forma, como uma classe (a comparação não é exata, mas pode servir-lhe de ajuda para lembrar-se de como se definir os enums com escopo).

O comportamento desses enums como classe ao qual me refiro pode ser visto na segunda diferença mostrada no exemplo #3 (linha 18): para se acessar um elemento do enum (ou um enumerador), é preciso precedê-lo do nome do enum e do operador de resolução de escopo :: ; no nosso exemplo, para acessar o valor Facil do enum é preciso escrever DificuldadeDoJogo::Facil. Note também que não é possível comparar um objeto do tipo DificuldadeDoJogo com um inteiro, como é feito na linha 20 do exemplo #2: minhaDificuldade == 0. Isso se dá porque conversões implícitas de um enumerador para um inteiro não são permitidas com os enums com escopo.

Por essa mesma razão, da impossibilidade de conversões implícitas entre enumeradores e tipos numéricos, é preciso converter explicitamente, no exemplo #3, a variável minhaDificuldade para um inteiro usando um static_cast<int> (linha 21). Sem a conversão explícita, o compilador emite um erro dizendo que não há uma versão do operador<< (usado sobre o objeto cout) que aceite o tipo do enum como argumento – se a conversão implícita fosse possível, o objeto de tipo enum seria convertido para um tipo numérico (como um int) e o cout << funcionaria normalmente.

Vantagem dos enums com escopo

A primeira vantagem do uso de enums com escopo é a criação de um escopo dedicado ao enum que não polui o escopo principal do programa (ou o escopo no qual o enum é criado). Tamando o mesmo cenário do exemplo #2, agora não temos mais erros se tentarmos criar uma variável cujo nome é igual a um dos elementos do enum, como podemos ver no exemplo abaixo.

// Exemplo #4 - O que é enum em C++
// Enums com escopo permitem o uso dos nomes dos seus
// elementos no nome de varáveis dentro do escopo que os contém.

#include <iostream>
#include <string>
using namespace std;

int main () {
    
    enum class DificuldadeDoJogo {
    Facil, // Valor 0
    Medio, // Valor 1
    Dificil, // Valor 2
    Impossivel = 15 // Valor 15
    };
    
    DificuldadeDoJogo minhaDificuldade {DificuldadeDoJogo::Facil};
    string Medio {"Medium"}; // Nenhum erro: essa linha funciona sem problemas
    if (minhaDificuldade == DificuldadeDoJogo::Facil) {
        cout << "A minha dificuldade é " << static_cast<int>(minhaDificuldade) << endl;
    }
}

Dica: using enum

Uma “desvantagem” do uso dos enumeradores com escopo é a poluição do código no que diz respeito à legibilidade: ter que usar o nome do enum seguido do operador de resolução de escopo (::) toda vez que for preciso acessar um dos elementos do enum pode tornar o código mais difícil de se ler (além de ser algo entediante de se fazer).

Contudo, há uma alternativa que permite evitar o uso de algo como DificuldadeDoJogo:: toda vez que se desejar acessar um elemento do enum: as declarações using enum. Com a seguinte sintaxe, é possível trazer os elementos do enum para o escopo atual e acessá-los somente através do seu nome (como mostrado no exemplo #5).

// Exemplo #5 - O que é enum em C++
// using enum declaration 

#include <iostream>
#include <string>
using namespace std;

enum class DificuldadeDoJogo {
    Facil, // Valor 0
    Medio, // Valor 1
    Dificil, // Valor 2
    Impossivel = 15 // Valor 15
    };

int main () {
    using enum DificuldadeDoJogo;
    
    DificuldadeDoJogo minhaDificuldade {Facil};
    if (minhaDificuldade == Facil) {
        cout << "A minha dificuldade é " << static_cast<int>(minhaDificuldade) << endl;
    }
}

Contudo, não existe almoço grátis: por trazer os elementos do enum para o escopo em questão, a declaração using enum DificuldadeDoJogo reintroduz no nosso programa o efeito de poluição do escopo causado pelos enums sem escopo. Logo, se eu quiser criar uma variável chamada Medio como fiz no exemplo #4, e ao mesmo tempo usar using enum DificuldadeDoJogo, eu terei um erro de compilação por tentar redefinir um nome que já está em uso pelo programa. Podemos ver esse problema no exemplo a seguir.

// Exemplo #6 - O que é enum em C++
// using enum declaration causando poluicao de escopo 

#include <iostream>
#include <string>
using namespace std;

enum class DificuldadeDoJogo {
    Facil, // Valor 0
    Medio, // Valor 1
    Dificil, // Valor 2
    Impossivel = 15 // Valor 15
    };

int main () {
    using enum DificuldadeDoJogo;
    DificuldadeDoJogo minhaDificuldade {Facil};
    string Medio {"Medium"}; // ERRO - declaracao conflitante com um enumerador em escopo
    if (minhaDificuldade == Facil) {
        cout << "A minha dificuldade é " << static_cast<int>(minhaDificuldade) << endl;
    }
}
Foto de perfil de Emanoel

Sou apaixonado por tecnologia, literatura e também filosofia. O cultivo dessas paixões ao longo da minha trajetória me inspiraram a compartilhar aquilo que aprendo com os outros da maneira mais clara que eu possa. Sou formado em Engenharia Elétrica no Brasil, e também sou engenheiro formado na França. Trabalho atualmente como programador C++ em uma multinacional francesa, uma das maiores empresas de TI do mundo.

Leave a Reply

Your email address will not be published. Required fields are marked *