2011-01-25 6 views
12

Diciamo che ho qualcosa di simile a questo:Rilevare se esprimono un int a un risultato di enum in un valore non enumerato

enum CardColor { HEARTS, DIAMONDS, CLUBS, SPADES}; 

CardColor MyColor = static_cast<CardColor>(100); 

C'è un modo (semplice) per rilevare, sia al momento della compilazione o runtime, che il valore di MyColor non corrisponde a nessun valore enumerato?

E più in generale, se i valori enum non seguono l'altro, per esempio:

enum CardColor { HEARTS = 0, DIAMONDS, CLUBS = 4, SPADES}; 
+2

In fase di runtime o in fase di compilazione? – CashCow

+3

Oltre a "non farlo"? –

+0

@CashCow: bene, entrambi! Ho aggiornato la mia domanda. –

risposta

15

CashCow presenta a decent answer a questa domanda: è certamente semplice scrivere una funzione personalizzata per eseguire un cast controllato.

Purtroppo, è anche un sacco di lavoro e si deve fare in modo di mantenerlo sincronizzato con l'enumerazione in modo che l'elenco dei enumeratori nella definizione enumerazione è la stessa della lista di enumeratori nella funzione getto controllato. Devi anche scrivere uno di questi per ogni enumerazione a cui vuoi essere in grado di eseguire un cast controllato.

Invece di fare tutto questo lavoro manuale, possiamo automatizzare la generazione di tutto questo codice usando il preprocessore (con un piccolo aiuto dalla libreria Boost Preprocessor). Ecco una macro che genera una definizione di enumerazione insieme a una funzione checked_enum_cast. Probabilmente è un po 'spaventoso (le macro di generazione del codice sono spesso orribili da considerare), ma è una tecnica estremamente utile con cui familiarizzare.

#include <stdexcept> 
#include <boost/preprocessor.hpp> 

// Internal helper to provide partial specialization for checked_enum_cast 
template <typename Target, typename Source> 
struct checked_enum_cast_impl; 

// Exception thrown by checked_enum_cast on cast failure 
struct invalid_enum_cast : std::out_of_range 
{ 
    invalid_enum_cast(const char* s) 
     : std::out_of_range(s) { } 
}; 

// Checked cast function 
template <typename Target, typename Source> 
Target checked_enum_cast(Source s) 
{ 
    return checked_enum_cast_impl<Target, Source>::do_cast(s); 
} 

// Internal helper to help declare case labels in the checked cast function 
#define X_DEFINE_SAFE_CAST_CASE(r, data, elem) case elem: 

// Macro to define an enum with a checked cast function. name is the name of 
// the enumeration to be defined and enumerators is the preprocessing sequence 
// of enumerators to be defined. See the usage example below. 
#define DEFINE_SAFE_CAST_ENUM(name, enumerators)       \ 
    enum name                \ 
    {                  \ 
     BOOST_PP_SEQ_ENUM(enumerators)          \ 
    };                  \ 
                      \ 
    template <typename Source>            \ 
    struct checked_enum_cast_impl<name, Source>       \ 
    {                  \ 
     static name do_cast(Source s)          \ 
     {                 \ 
      switch (s)              \ 
      {                \ 
      BOOST_PP_SEQ_FOR_EACH(X_DEFINE_SAFE_CAST_CASE, 0, enumerators) \ 
       return static_cast<name>(s);        \ 
      default:              \ 
       throw invalid_enum_cast(BOOST_PP_STRINGIZE(name));   \ 
      }                \ 
      return name();             \ 
     }                 \ 
    }; 

Ecco come si usa che con il vostro CardColor esempio:

DEFINE_SAFE_CAST_ENUM(CardColor, (HEARTS) (CLUBS) (SPADES) (DIAMONDS)) 

int main() 
{ 
    checked_enum_cast<CardColor>(1); // ok 
    checked_enum_cast<CardColor>(400); // o noez! an exception! 
} 

La prima riga sostituisce la tua definizione enum CardColor ...; definisce l'enumerazione e fornisce una specializzazione che consente di utilizzare checked_enum_cast per eseguire il cast degli interi su CardColor.

Questo può sembrare un sacco di problemi solo per ottenere una funzione di cast controllata per l'enumerazione, ma questa tecnica è molto utile ed estensibile. Puoi aggiungere funzioni che fanno ogni sorta di cose. Ad esempio, ne ho uno che genera funzioni per convertire tipi enumerati in e da rappresentazioni di stringa e funzioni che eseguono diverse altre conversioni e controlli che utilizzo per la maggior parte delle mie enumerazioni.

Ricordare, è necessario scrivere e eseguire il debug di quella macro grande e brutta solo una volta, quindi è possibile utilizzarla ovunque.

+4

+1 per l'uso di "o noez" –

3

È comune avere un elemento aggiuntivo alla fine del enumerazione che indica il numero di elementi in esso. È possibile utilizzare questo valore per verificare in fase di esecuzione se il valore è valido:

enum CardColor { HEARTS, DIAMONDS, CLUBS, SPADES, CARDS_COUNT}; 

CardColor MyColor = static_cast<CardColor>(100); 

if (MyColor >= CARDS_COUNT) { 
    /* Invalid value */ 
} 
+3

No, non lo è. Vuoi davvero un CardColor accettabile per essere CARDS_COUNT? – wheaties

+5

@wheaties In realtà, qualsiasi numero intero è un CardColor accettabile. Ecco perché questa domanda è stata posta in primo luogo. – vz0

+1

Penso che un problema è che 'static_cast (100);' innesca un comportamento indefinito, e ciò significa che in teoria il valore restituito potrebbe essere anche CUORI o DIAMANTI. Vedi http://stackoverflow.com/a/33608071/2436175 – Antonio

1

All'inizio - è cattiva idea di fare questo.

Ma se vuoi, mi suggeriscono di hardcode valori interi di enumerazione:

enum CardColor { HEARTS = 10, DIAMONDS = 11, CLUBS = 12, SPADES = 13}; 

Poi sovraccaricare operatore assigment:

CardColor operator = (int value) 
{ 
    switch (value) 
    { 
     case 10: 
      return HEARTS; 
     // case for other values 
     default: 
      // throw an exception or something 
    } 
} 
+0

mentre questo fa tecnicamente ciò che l'OP vuole, invalida completamente anche il punto di usare un enum. @ La risposta di CashCow è simile, ma molto meglio. – tenfour

8

più semplice run-time soluzione sarebbe quella di non usare static_cast ma l'uso una funzione che fa il controllo per te. Se metti il ​​tuo enum in una classe puoi farlo attraverso la classe. Qualcosa di simile:

class CardCheck 
{ 
public: 
    enum CardColor { HEARTS, DIAMONDS, CLUBS, SPADES }; 

    explicit CardCheck(int x) : c(static_cast<CardColor>(x)) 
    { 
    switch(c) 
    { 
     case HEARTS: case DIAMONDS: case CLUBS: case SPADES: 
      break; 

     default: 
     // assert or throw 
    } 
    } 

    CardColor get() const 
    { 
    return c; 
    } 

private: 
    CardColor c;  
}; 
+1

Il cast dovrebbe probabilmente aver luogo dopo il controllo: potrebbe potenzialmente causare un overflow (credo). –

+2

È interessante notare che abbiamo fatto qualcosa del genere nel nostro codice per convalidare i numeri interi inseriti in enumerazioni. Un compilatore ha deciso di ottimizzare l'istruzione switch, presumibilmente perché tutti i possibili casi erano coperti, quindi ha ritenuto che fosse sempre valido. Forse un buon motivo per includere CARD_COUNT. Quindi esiste almeno un possibile valore errato e il compilatore non ottimizzerà. – CashCow

0

valori di enumerazione possono sovrapporsi o hanno le loro tane; inoltre, le variabili attuali possono essere assegnate a zero, qualsiasi valore dall'insieme o l'OR bit a bit dei valori consentiti. Quindi:

enum suites { hearts, diamonds, clubs, spades }; 

consente i valori 0, 1, 2, 3;

enum suites { hearts = 1 << 0, diamonds = 1 << 1, clubs = 1 << 2, spades = 1 << 4 }; 

consente a qualsiasi valore da 0 a 15.

Se si utilizza un enum per definire valori di bit, di solito è una buona idea per definire (binario) operator&, operator|, operator&= e operator|=. Se non lo fai, avrai bisogno di un cast esplicito ogni volta che viene generato un valore che non è nel set, quindi i punti in cui questo accade possono essere facilmente individuati.

ci sono compilatori in grado di avvertire se viene assegnato un numero al di fuori del range consentito, o se non sia nessuno, il primo o tutti i nomi hanno inizializzatori ad essi connessi (questa è una violazione di MISRA-C regole).

+0

in modo che il jolly sia 0 (nessun seme) o 15 (tutti e 4)? – CashCow

+0

Sì. –

4

clang hanno il supporto per i controlli di overflow dinamici. Vedere l'interruttore -fsanitize=enum. Un programma compilato con questo interruttore segnalerà errori di assegnazione enum attraverso l'output stderr. Questo ti permetterà di fare test di debug. Non è adatto per testare input sospetti in build ufficiali.

Problemi correlati