Menu

Enum in C++: enum class ed enum semplice a confronto

Impara gli enum del C++: come dichiararli, perché l'enum class con scope è più sicuro dell'enum semplice, valori personalizzati, switch sugli enumeratori e conversione da e verso interi.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

A cosa serve un enum

Una struct raggruppa più valori correlati in un unico oggetto. Un enum risolve un problema diverso: dà un nome a un insieme piccolo e fisso di scelte. Invece di ricordarti che 0 significa "rosso", 1 significa "verde" e 2 significa "blu", scrivi Color::Red e il compilatore ti tiene sulla retta via.

Usare un enum ogni volta che una variabile può trovarsi solo in uno fra pochi stati con un nome, come il colore di un semaforo, il seme di una carta o lo stato di una connessione, rende il codice autoesplicativo e permette al compilatore di intercettare errori di battitura e casi mancanti che con dei semplici interi non vedrebbe mai.

Dichiarare un enum

Il C++ moderno ne ha due varianti. Parti da quella da usare quasi sempre: l'enum class con scope. Elenchi i nomi, e a ogni enumeratore si accede tramite il nome dell'enum con :::

Nota che scrivi Color::Green, mai un Green isolato. I valori in sé sono solo etichette: li confronti, li assegni e li passi in giro, ma raramente ti interessa il numero sottostante. Di default Red vale 0, Green vale 1 e Blue vale 2, contando a partire da zero.

enum semplice ed enum class

Il vecchio enum senza scope (senza la parola chiave class) mette i suoi nomi direttamente nello scope circostante e si converte in int da solo. Sembra comodo, ma causa due problemi reali:

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!
}

Dato che gli enumeratori semplici sono nomi globali, due enum possono entrare in conflitto solo perché condividono un'etichetta. E dato che decadono a int, il compilatore confronta tranquillamente valori di enum che non hanno nulla a che fare tra loro. Un enum class con scope risolve entrambi i problemi: i nomi vivono dentro il tipo, e il tipo non si trasforma in silenzio in un int:

La regola pratica: usa enum class di default. Ripiega su un enum semplice solo quando vuoi proprio la conversione implicita in int, come nelle vecchie costanti flag in stile C.

Valori personalizzati e tipo sottostante

Puoi assegnare numeri espliciti agli enumeratori. Quelli che lasci senza valore continuano a contare dal precedente, il che è comodo per cose come i codici di stato HTTP o i flag a bit:

Ogni enum si basa su un tipo intero, int di default. Puoi fissarlo a un tipo più piccolo quando la dimensione conta, per esempio per memorizzare molti enum in una struttura compatta o per rispettare un formato di trasmissione:

Scegliere il tipo sottostante garantisce anche l'intervallo in cui devono stare i tuoi valori: un enum uint8_t non può contenere un valore superiore a 255.

Convertire tra enum e int

Un enum class con scope non si converte mai implicitamente, ed è proprio questo il punto. Quando ti serve davvero il numero, per stamparlo, usarlo come indice di un array o leggerlo da un file, usa static_cast. Passare da enum a int è sempre sicuro:

Riconvertire un int in un enum è la direzione pericolosa. Il cast non controlla che il numero corrisponda a un enumeratore reale: ti restituisce un valore che tecnicamente sta fuori dall'insieme di nomi dell'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 l'intero arriva dall'input dell'utente o da un file, controlla tu l'intervallo prima del cast, altrimenti creerai un valore che nessun case gestisce mai: una fonte subdola di comportamento indefinito più avanti.

Usare gli enum con switch

Dato che un enum è "uno fra un insieme fisso", si abbina perfettamente a uno switch. Quando copri ogni enumeratore, molti compilatori ti avvisano se in seguito aggiungi un nuovo valore e ti dimentichi di gestirlo: una sicurezza gratuita che con gli interi grezzi non hai.

Un'insidia: non esiste un modo predefinito per stampare il nome di un enumeratore. cout << TrafficLight::Red non compila per un enum con scope, e anche per un enum semplice stampa il numero, non "Red". Un piccolo switch o una tabella di lookup come quella qui sopra è il modo abituale per trasformare un enum in una stringa leggibile.

Prossimo: Eccezioni

Enum e struct ti permettono di modellare come sono fatti i tuoi dati. Ma i programmi reali devono anche gestire le cose che vanno storte: un file che non si apre, un numero che non si riesce a interpretare, un valore fuori intervallo. Il C++ gestisce questi percorsi di errore con le eccezioni, ed è l'argomento della prossima pagina.

Domande frequenti

Qual è la differenza tra enum ed enum class in C++?

Un enum semplice riversa i suoi nomi nello scope circostante e si converte implicitamente in int, causando conflitti di nomi e confronti accidentali. Un enum class con scope tiene i suoi nomi dentro l'enum (Color::Red) e si rifiuta di convertirsi in int senza un cast esplicito. Nel C++ moderno preferisci enum class: è type-safe ed evita le insidie classiche.

Come si converte un enum C++ in un int?

Un enum semplice si converte implicitamente, quindi int n = Red; funziona e basta. Un enum class con scope richiede un cast esplicito: int n = static_cast<int>(Color::Red);. Per la direzione opposta, riconverti l'intero con un cast: Color c = static_cast<Color>(2);, ma fai attenzione, perché a runtime nessuno controlla che il valore sia un enumeratore valido.

Da quale valore parte il primo enum in C++?

Di default il primo enumeratore vale 0 e ognuno dei successivi vale uno in più del precedente. Quindi in enum class Level { Low, Mid, High };, Low vale 0, Mid vale 1 e High vale 2. Puoi assegnare valori espliciti a qualsiasi enumeratore per cambiare questo comportamento.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA