Quale Ente Typesafe in C ++ stai usando?

È risaputo che le enumerazioni incorporate in C ++ non sono typesafe. Mi stavo chiedendo quali sono le classi che implementano le enumerate enumerate … Io uso la seguente “bicicletta”, ma è alquanto prolissa e limitata:

typesafeenum.h:

struct TypesafeEnum { // Construction: public: TypesafeEnum(): id (next_id++), name("") {} TypesafeEnum(const std::string& n): id(next_id++), name(n) {} // Operations: public: bool operator == (const TypesafeEnum& right) const; bool operator != (const TypesafeEnum& right) const; bool operator < (const TypesafeEnum& right) const; std::string to_string() const { return name; } // Implementation: private: static int next_id; int id; std::string name; }; 

typesafeenum.cpp:

 int TypesafeEnum::next_id = 1; bool TypesafeEnum::operator== (const TypesafeEnum& right) const { return id == right.id; } bool TypesafeEnum::operator!= (const TypesafeEnum& right) const { return !operator== (right); } bool TypesafeEnum::operator< (const TypesafeEnum& right) const { return id < right.id; } 

Uso:

 class Dialog { ... struct Result: public TypesafeEnum { static const Result CANCEL("Cancel"); static const Result OK("Ok"); }; Result doModal(); ... }; const Dialog::Result Dialog::Result::OK; const Dialog::Result Dialog::Result::CANCEL; 

Aggiunta: penso che avrei dovuto essere più specifico sui requisiti. Cercherò di riassumerli:

Priorità 1: impostare una variabile enum su un valore non valido dovrebbe essere imansible (un errore in fase di compilazione) senza eccezioni.

Priorità 2: la conversione di un valore enum in / da un int dovrebbe essere ansible con una singola chiamata di metodo / funzione esplicita.

Priorità 3: dichiarazione e utilizzo il più ansible compatti, eleganti e convenienti

Priorità 4: conversione dei valori enum in e da stringhe.

Priorità 5: (Bello da avere) Possibilità di iterare su valori enum.

    Attualmente sto giocando con la proposta Boost.Enum del Boost Vault (nome file enum_rev4.6.zip ). Sebbene non sia mai stato ufficialmente presentato per l’inclusione in Boost, è utilizzabile così com’è. (Manca la documentazione ma è compensata da un codice sorgente chiaro e buoni test).

    Boost.Enum ti permette di dichiarare un enume come questo:

     BOOST_ENUM_VALUES(Level, const char*, (Abort)("unrecoverable problem") (Error)("recoverable problem") (Alert)("unexpected behavior") (Info) ("expected behavior") (Trace)("normal flow of execution") (Debug)("detailed object state listings") ) 

    E farlo espandere automaticamente a questo:

     class Level : public boost::detail::enum_base { public: enum domain { Abort, Error, Alert, Info, Trace, Debug, }; BOOST_STATIC_CONSTANT(index_type, size = 6); Level() {} Level(domain index) : boost::detail::enum_base(index) {} typedef boost::optional optional; static optional get_by_name(const char* str) { if(strcmp(str, "Abort") == 0) return optional(Abort); if(strcmp(str, "Error") == 0) return optional(Error); if(strcmp(str, "Alert") == 0) return optional(Alert); if(strcmp(str, "Info") == 0) return optional(Info); if(strcmp(str, "Trace") == 0) return optional(Trace); if(strcmp(str, "Debug") == 0) return optional(Debug); return optional(); } private: friend class boost::detail::enum_base; static const char* names(domain index) { switch(index) { case Abort: return "Abort"; case Error: return "Error"; case Alert: return "Alert"; case Info: return "Info"; case Trace: return "Trace"; case Debug: return "Debug"; default: return NULL; } } typedef boost::optional optional_value; static optional_value values(domain index) { switch(index) { case Abort: return optional_value("unrecoverable problem"); case Error: return optional_value("recoverable problem"); case Alert: return optional_value("unexpected behavior"); case Info: return optional_value("expected behavior"); case Trace: return optional_value("normal flow of execution"); case Debug: return optional_value("detailed object state listings"); default: return optional_value(); } } }; 

    Soddisfa tutte e cinque le priorità che elencherai.

    Un buon metodo di compromesso è questo:

     struct Flintstones { enum E { Fred, Barney, Wilma }; }; Flintstones::E fred = Flintstones::Fred; Flintstones::E barney = Flintstones::Barney; 

    Non è tipicamente nello stesso senso in cui è la tua versione, ma l’utilizzo è più piacevole delle enumerazioni standard e puoi comunque avvantaggiarti della conversione dei numeri interi quando ne hai bisogno.

    Io uso enunti typesafe C ++ 0x . Uso alcuni template / macro helper che forniscono la funzionalità to / from string.

     enum class Result { Ok, Cancel}; 

    Io non. Troppo troppo alto per poco beneficio. Inoltre, essere in grado di convertire le enumerazioni in diversi tipi di dati per la serializzazione è uno strumento molto utile. Non ho mai visto un’istanza in cui un’enumerazione di tipo “sicuro” varrebbe la pena e la complessità in cui C ++ offre già un’implementazione sufficientemente buona.

    La mia opinione è che stai inventando un problema e poi inserendo una soluzione su di esso. Non vedo la necessità di fare un quadro elaborato per un’enumerazione di valori. Se ti stai dedicando a far sì che i tuoi valori siano solo membri di un determinato set, potresti creare una variante di un tipo di dati set univoco.

    Sto usando personalmente una versione adattata dell’idioma typesafe enum . Non fornisce tutti i cinque “requisiti” che hai indicato nella tua modifica, ma non sono assolutamente d’accordo con alcuni di essi. Ad esempio, non vedo come Prio # 4 (conversione di valori in stringhe) abbia qualcosa a che fare con la sicurezza del tipo. La maggior parte delle volte la rappresentazione di stringa dei singoli valori dovrebbe essere comunque separata dalla definizione del tipo (si pensi a i18n per una semplice ragione). Prio # 5 (iteratio, che è facoltativo) è una delle cose più belle che mi piacerebbe vedere accadesse naturalmente nelle enumerazioni, quindi mi sentivo triste che appaia come “opzionale” nella tua richiesta, ma sembra che sia meglio indirizzato tramite un sistema di iterazione separato come le funzioni begin / end o enum_iterator, che li rende perfettamente compatibili con foreach STL e C ++ 11.

    OTOH questo semplice idioma fornisce Prio # 3 Prio # 1 grazie al fatto che per lo più include solo enum con più informazioni sul tipo. Per non dire che è una soluzione molto semplice che per la maggior parte non richiede alcun header di dipendenza esterno, quindi è abbastanza facile da portare in giro. Ha anche il vantaggio di rendere le enumerazioni con scope a-la-C ++ 11:

     // This doesn't compile, and if it did it wouldn't work anyway enum colors { salmon, .... }; enum fishes { salmon, .... }; // This, however, works seamlessly. struct colors_def { enum type { salmon, .... }; }; struct fishes_def { enum type { salmon, .... }; }; typedef typesafe_enum colors; typedef typesafe_enum fishes; 

    L’unico “buco” che la soluzione fornisce è che non affronta il fatto che non impedisce l’ enum di tipi diversi (o un enum e un int) dal confronto diretto, perché quando si usano i valori direttamente si forza il conversione implicita in int :

     if (colors::salmon == fishes::salmon) { .../* Ooops! */... } 

    Ma finora ho trovato che questi problemi possono essere risolti semplicemente offrendo un confronto migliore con il compilatore – ad esempio, fornendo esplicitamente un operatore che confronta due tipi di enum diversi, quindi costringendolo a fallire:

     // I'm using backports of C++11 utilities like static_assert and enable_if template  typename enable_if< (is_enum::value && is_enum::value) && (false == is_same::value) , bool > ::type operator== (Enum1, Enum2) { static_assert (false, "Comparing enumerations of different types!"); } 

    Sebbene non sembri rompere il codice finora, e lo fa per affrontare esplicitamente il problema specifico senza fare qualcos’altro, non sono sicuro che una cosa del genere sia una cosa che ” dovrebbe ” fare (sospetto che interferisca con enum già prendendo parte agli operatori di conversione dichiarati altrove, sarei lieto di ricevere commenti a riguardo).

    Combinare questo con l’idioma typesaf di cui sopra fornisce qualcosa che è relativamente vicino alla enum class enumatica C ++ 11 in termini di umanità (leggibilità e manutenibilità) senza dover fare qualcosa di troppo oscuro. E devo ammettere che è stato divertente, non avrei mai pensato di chiedere realmente al compilatore se avessi a che fare con enum o no …

    Penso che l’ enum Java sarebbe un buon modello da seguire. Essenzialmente, il modulo Java sarebbe simile a questo:

     public enum Result { OK("OK"), CANCEL("Cancel"); private final String name; Result(String name) { this.name = name; } public String getName() { return name; } } 

    L’aspetto interessante dell’approccio Java è che OK e CANCEL sono istanze immutabili, singleton di Result (con i metodi che vedi). Non è ansible creare ulteriori istanze di Result . Dal momento che sono singleton, puoi confrontare con pointer / reference — molto utile. 🙂

    ETA: in Java, invece di eseguire maschere di bit a mano, invece si utilizza un EnumSet per specificare un set di bit (implementa l’interfaccia Set e funziona come set — ma implementato usando le maschere di bit). Molto più leggibile della manipolazione della maschera di bit scritta a mano!

    Ho dato una risposta a questo qui , su un argomento diverso. È uno stile di approccio diverso che consente la maggior parte delle stesse funzionalità senza richiedere modifiche alla definizione di enum originale (e di conseguenza consente l’utilizzo nei casi in cui non si definisce l’enumerazione). Permette anche il controllo dell’intervallo di runtime.

    Il lato negativo del mio approccio è che non impone l’aghook programmatico tra l’enum e la class helper, quindi devono essere aggiornati in parallelo. Funziona per me, ma YMMV.

    Attualmente sto scrivendo la mia libreria enum tipesa su https://bitbucket.org/chopsii/typesafe-enums

    Non sono lo sviluppatore C ++ più esperto di sempre, ma sto scrivendo questo a causa delle carenze dell’enumerazione del vault BOOST.

    Sentiti libero di verificarlo e usalo tu stesso, ma hanno alcuni problemi di usabilità (si spera minori), e probabilmente non sono affatto trasversali.

    Per favore contribuisci se vuoi. Questa è la mia prima impresa open source.

    Usa boost::variant !

    Dopo aver provato molte delle idee di cui sopra e trovandole carenti, mi sono imbattuto in questo semplice approccio:

     #include  #include  struct A_t {}; static const A_t A = A_t(); template  bool isA(const T & x) { if(boost::get(&x)) return true; return false; } struct B_t {}; static const B_t B = B_t(); template  bool isB(const T & x) { if(boost::get(&x)) return true; return false; } struct C_t {}; static const C_t C = C_t(); template  bool isC(const T & x) { if(boost::get(&x)) return true; return false; } typedef boost::variant AB; typedef boost::variant BC; void ab(const AB & e) { if(isA(e)) std::cerr < < "A!" << std::endl; if(isB(e)) std::cerr << "B!" << std::endl; // ERROR: // if(isC(e)) // std::cerr << "C!" << std::endl; // ERROR: // if(e == 0) // std::cerr << "B!" << std::endl; } void bc(const BC & e) { // ERROR: // if(isA(e)) // std::cerr << "A!" << std::endl; if(isB(e)) std::cerr << "B!" << std::endl; if(isC(e)) std::cerr << "C!" << std::endl; } int main() { AB a; a = A; AB b; b = B; ab(a); ab(b); ab(A); ab(B); // ab(C); // ERROR // bc(A); // ERROR bc(B); bc(C); } 

    Probabilmente puoi inventare una macro per generare il boilerplate. (Fammi sapere se lo fai.)

    A differenza di altri approcci, questo è effettivamente sicuro per il tipo e funziona con il vecchio C ++. Puoi anche creare tipi fantastici come boost::variant , ad esempio, per rappresentare un valore che può essere A, B, un numero intero o niente che è quasi Haskell98 livelli di sicurezza del tipo .

    Svantaggi di essere a conoscenza di:

    • almeno con il vecchio boost - Sono su un sistema con boost 1.33 - sei limitato a 20 elementi nella tua variante; c'è comunque un work-around
    • influisce sul tempo di compilazione
    • messaggi di errore insani - ma questo è C ++ per te

    Aggiornare

    Qui, per tua comodità, troverai la tua "biblioteca" tipicamente enumatica. Incolla questa intestazione:

     #ifndef _TYPESAFE_ENUMS_H #define _TYPESAFE_ENUMS_H #include  #include  #define ITEM(NAME, VAL) \ struct NAME##_t { \ std::string toStr() const { return std::string( #NAME ); } \ int toInt() const { return VAL; } \ }; \ static const NAME##_t NAME = NAME##_t(); \ template  \ bool is##NAME(const T & x) { if(boost::get(&x)) return true; return false; } \ class toStr_visitor: public boost::static_visitor { public: template std::string operator()(const T & a) const { return a.toStr(); } }; template inline static std::string toStr(const boost::variant & a) { return boost::apply_visitor(toStr_visitor(), a); } class toInt_visitor: public boost::static_visitor { public: template int operator()(const T & a) const { return a.toInt(); } }; template inline static int toInt(const boost::variant & a) { return boost::apply_visitor(toInt_visitor(), a); } #define ENUM(...) \ typedef boost::variant<__va_args__> #endif 

    E usalo come:

     ITEM(A, 0); ITEM(B, 1); ITEM(C, 2); ENUM(A_t, B_t) AB; ENUM(B_t, C_t) BC; 

    Nota che devi dire A_t invece di A nella macro ENUM che distrugge parte della magia. Oh bene. Inoltre, notate che ora c'è una funzione toStr e una funzione toInt per soddisfare i requisiti OP di una semplice conversione in stringhe e int. Il requisito che non riesco a capire è un modo per scorrere gli articoli. Fammi sapere se sai come scrivere una cosa del genere.

    Non sono sicuro che questo post sia troppo tardi, ma c’è un articolo su GameDev.net che soddisfa tutti tranne il 5 ° punto (possibilità di scorrere gli enumeratori): http://www.gamedev.net/reference/snippets/features/cppstringizing/

    Il metodo descritto dall’articolo consente il supporto per la conversione delle stringhe per le enumerazioni esistenti senza modificare il loro codice. Se si desidera il supporto per le nuove enumerazioni, andrei con Boost.Enum (menzionato sopra).