Ir para o conteúdo

💡 Resumo:Tutorial de enum em C++: enums clássicos, enum class do C++11, enums dentro de classes e como a reflexão do C++26 (std::meta) finalmente resolve a conversão de enum para string sem macros nem tabelas de busca.

Última atualização:2026-10-24

Tipos de enumeração (enum) em C++

Sobre o autor:

Conta oficial do WeChat:

Tipos de enumeração (enum) em C++ | C++那些事

Comportamento tradicional

As enumerações têm os seguintes problemas:

  • O escopo é ilimitado, o que facilita conflitos de nomenclatura. Por exemplo, o código a seguir não compila:

enum Color
{
    RED,
    BLUE
};
enum Feeling
{
    EXCITED,
    BLUE
};

int main()
{
    Color a = BLUE; // error
    Feeling b = EXCITED;
    std::cout << a << ":" << b << std::endl;
    return 0;
}
- Conversão implícita para int - O tipo real usado para representar a variável de enumeração não pode ser especificado explicitamente, o que impede a declaração antecipada de tipos de enumeração.

A implementação concreta está em: tradition_color.cpp

Prática clássica

Uma forma simples de resolver os conflitos de nomenclatura causados pelo escopo ilimitado é adicionar um prefixo ao nomear as variáveis de enumeração, como no exemplo acima, alterando para COLOR_BLUE e FEELING_BLUE.

Em geral, por consistência, adicionamos o prefixo a todas as constantes. Mas, assim, o código que define as variáveis de enumeração fica prolixo. Em programas C, talvez seja inevitável fazer assim; programadores C++, porém, dificilmente apreciam esse método. A alternativa são os namespaces:

namespace Color 
{
    enum Type
    {
        RED=15,
        YELLOW,
        BLUE
    };
};

Depois disso, é possível definir uma nova variável de enumeração com Color::Type c = Color::RED;. Se for usado using namespace Color, o prefixo pode até ser omitido, simplificando o código. No entanto, como um namespace pode ser estendido posteriormente com novos conteúdos, o fechamento de escopo que ele oferece não é alto. Em projetos grandes, ainda é possível que pessoas diferentes dêem o mesmo nome de tipo de enumeração a coisas diferentes.

A forma mais "eficaz" é usar uma classe ou estrutura para restringir o escopo: por exemplo, o método para definir novas variáveis é o mesmo do namespace acima; a diferença é que assim não precisamos nos preocupar com a classe sendo modificada em outro lugar. Usa-se uma estrutura, e não uma classe, porque justamente se espera que essas constantes sejam publicamente acessíveis.

struct Color1
{
    enum Type
    {
        RED=102,
        YELLOW,
        BLUE
    };
};

A implementação concreta está em: classic_practice.cpp

As enum classes do C++11

A abordagem acima resolve o primeiro problema, mas continua impotente diante dos outros dois. Felizmente, o padrão C++11 introduziu as "enum classes" (enum class), que resolvem bem os problemas mencionados.

  • O escopo do novo enum deixa de ser global
  • Não há conversão implícita para outros tipos
/**
 * @brief C++11的枚举类
 * 下面等价于enum class Color2:int
 */
enum class Color2
{
    RED=2,
    YELLOW,
    BLUE
};
Color2 c2 = Color2::RED;
cout << static_cast<int>(c2) << endl; //必须转!
  • É possível especificar um tipo específico para armazenar o enum
enum class Color3:char;  // 前向声明

// 定义
enum class Color3:char 
{
    RED='r',
    BLUE
};
char c3 = static_cast<char>(Color3::RED);

A implementação concreta está em: classic_practice.cpp

Tipos de enumeração dentro de classes

Às vezes queremos que certas constantes sejam válidas apenas dentro da classe. Como as constantes de macro definidas por #define são globais e não atendem ao objetivo, pensamos em usar const para modificar membros de dados. De fato, os membros de dados const existem, mas seu significado não é o que esperamos.

Um membro de dados const é constante apenas durante o tempo de vida de um objeto; para a classe inteira, ele é variável, porque a classe pode criar vários objetos, e o valor do membro de dados const pode ser diferente em objetos diferentes.

Não é possível inicializar um membro de dados const na declaração da classe. O uso a seguir está errado, porque, enquanto o objeto da classe não for criado, o compilador não sabe qual é o valor de SIZE. (antes do padrão C++11)

class A 
{
  const int SIZE = 100;   // 错误,企图在类声明中初始化 const 数据成员 
  int array[SIZE];  // 错误,未知的 SIZE 
}; 

O correto é fazê-lo na lista de inicialização do construtor da classe:

class A 
{
  A(int size);  // 构造函数 
  const int SIZE ;    
}; 
A::A(int size) : SIZE(size)  // 构造函数的定义
{ 

} 
A  a(100); // 对象 a 的 SIZE 值为 100 
A  b(200); // 对象 b 的 SIZE 值为 200 

Como estabelecer, então, uma constante fixa em toda a classe?

Não conte com membros de dados const; use constantes de enumeração dentro da classe. Por exemplo:

class Person{
public:
    typedef enum {
        BOY = 0,
        GIRL
    }SexType;
};
//访问的时候通过,Person::BOY或者Person::GIRL来进行访问。

As constantes de enumeração não ocupam espaço de armazenamento dos objetos; elas são todas avaliadas em tempo de compilação.

A desvantagem das constantes de enumeração é que seu tipo de dados implícito é o inteiro, com valor máximo limitado e sem capacidade de representar números de ponto flutuante.

De enum para string: a resposta da reflexão do C++26

Converter um valor de enum no seu nome é um problema que quase todo projeto C++ acaba encontrando, e até o C++26 a biblioteca padrão não tinha resposta. As alternativas, da mais antiga para a mais recente: (1) X-macros, que abusam do pré-processador para manter a lista de enumeradores num só lugar; (2) tabelas de busca escritas à mão ou especialização de templates, onde cada enum novo significa mais uma tabela para manter; (3) bibliotecas de terceiros como magic_enum, que extraem os nomes com truques de compilador, mas impõem limites de tamanho e tipo subjacente.

A reflexão estática do C++26 (proposta P2996, std::meta) finalmente transforma isso em poucas linhas de código de verdade. Uma função enumera os membros de qualquer enum em tempo de compilação e lê o nome de cada enumerador:

#include <meta>

template <typename E>
    requires std::is_enum_v<E>
std::string enum_to_string(E value) {
    template for (constexpr auto e : std::meta::enumerators_of(^^E)) {
        if (value == [:e:]) {
            return std::meta::identifier_of(e);
        }
    }
    throw std::invalid_argument("unknown enumerator");
}

Uma única função passa a cobrir todos os enums do código. Renomear um enumerador atualiza a string automaticamente, e uma divergência aparece na compilação em vez de virar um rótulo errado em runtime.

Um aviso honesto: o trecho segue a P2996 conforme entrou no draft do C++26. As grafias exatas ainda estavam se assentando nas previews dos compiladores (EDG e Clang explorers), então confira a tabela de suporte da sua toolchain antes de usar. Até lá, a tabela de busca continua sendo a resposta mais portátil e sem dependências.

Perguntas frequentes

Como converto um enum para string em C++?

Antes do C++26 não existe forma padrão: escolha entre tabela de busca, especialização de templates, X-macros ou uma biblioteca como magic_enum. A reflexão estática do C++26 enumera os membros em compilação e lê o nome diretamente, eliminando a manutenção das quatro.

A biblioteca padrão tem conversão embutida de enum para string?

Só no C++26. Streams e std::to_string formatam apenas o inteiro subjacente, e é por isso que todo projeto acaba escrevendo o próprio helper.

Quando vou poder usar a reflexão do C++26?

O C++26 é o próximo padrão e a reflexão já está no draft; EDG e os explorers do Clang já compilam os exemplos. Para produção, acompanhe o suporte do compilador e mantenha uma tabela de busca como fallback.

Tópicos relacionados

Comentários