Domanda

È risaputo che gli enum incorporati in C ++ non sono sicuri. Mi chiedevo quali classi implementassero enumerazioni typesafe siano usate là fuori ... Io stesso uso il seguente & Quot; bicycle & Quot ;, ma è in qualche modo dettagliato e limitato:

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

Utilizzo:

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. Proverò a sintetizzarli:

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

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

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

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

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

È stato utile?

Soluzione

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

Boost.Enum ti consente di dichiarare un enum 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 fallo espandere automaticamente a questo:

class Level : public boost::detail::enum_base<Level, string>
{
public:
    enum domain
    {
        Abort,
        Error,
        Alert,
        Info,
        Trace,
        Debug,
    };

    BOOST_STATIC_CONSTANT(index_type, size = 6);

    Level() {}
    Level(domain index) : boost::detail::enum_base<Level, string>(index) {}

    typedef boost::optional<Level> 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<Level, string>;
    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<value_type> 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 elenchi.

Altri suggerimenti

Un buon metodo di compromesso è questo:

struct Flintstones {
   enum E {
      Fred,
      Barney,
      Wilma
   };
};

Flintstones::E fred = Flintstones::Fred;
Flintstones::E barney = Flintstones::Barney;

Non è un errore tipografico nello stesso modo in cui lo è la tua versione, ma l'utilizzo è migliore rispetto agli enum standard e puoi comunque sfruttare la conversione di numeri interi quando ne hai bisogno.

Uso C ++ 0x typesafe enums . Uso alcuni template / macro di supporto che forniscono la funzionalità da / a stringa.

enum class Result { Ok, Cancel};

Non lo so. Troppo sovraccarico per pochi benefici. Inoltre, essere in grado di eseguire il cast di enumerazioni a diversi tipi di dati per la serializzazione è uno strumento molto utile. Non ho mai visto un'istanza in cui un & Quot; Digitare safe & Quot; l'enumerazione varrebbe il sovraccarico e la complessità in cui C ++ offre già un'implementazione abbastanza buona.

La mia opinione è che stai inventando un problema e quindi inserendo una soluzione su di esso. Non vedo la necessità di fare un elaborato framework per un elenco di valori. Se sei dedicato a far sì che i tuoi valori siano solo membri di un determinato set, puoi hackerare una variante di un tipo di dati set univoco.

Uso personalmente una versione adattata del typesafe enum idiom . Non fornisce tutti i cinque & Quot; requisiti & Quot; che hai dichiarato 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 dei tipi. La maggior parte della rappresentazione della stringa di tempo di singoli valori dovrebbe essere comunque separata dalla definizione del tipo (si pensi a i18n per un semplice motivo). Prio # 5 (iteratio, che è facoltativo) è una delle cose più belle che mi piacerebbe vedere naturalmente accadere in enum, quindi mi sentivo triste che appare come & Quot; opzionale quot; nella tua richiesta, ma sembra che sia meglio affrontato tramite un sistema di iterazione separato come begin / end funzioni o un enum_iterator, che li rende perfettamente compatibili con STL e C ++ 11 foreach.

OTOH questo semplice idioma fornisce Prio # 3 Prio # 1 grazie al fatto che per lo più avvolge enum solo con più informazioni sul tipo. Per non parlare del fatto che si tratta di una soluzione molto semplice che per la maggior parte non richiede intestazioni di dipendenza esterne, quindi è abbastanza facile da portare in giro. Ha anche il vantaggio di creare enumerazioni con ambito 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_def> colors;
typedef typesafe_enum<fishes_def> fishes;

L'unica " hole " quella soluzione prevede che non risolve il fatto che non impedisce il confronto diretto tra int s di tipi diversi (o un enum class e un int), poiché quando si utilizzano direttamente i valori si forza l'implicito conversione in <=>:

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

Ma finora ho scoperto che tali problemi possono essere risolti semplicemente offrendo un confronto migliore al compilatore, ad esempio fornendo esplicitamente un operatore che confronta due diversi <=> tipi, quindi forzandolo a fallire:

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

Anche se non sembra infrangere 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 <<>> quot; & dovrebbe quot; sì (sospetto che interferirà con <=> già partecipanti agli operatori di conversione dichiarati altrove; sarei lieto di ricevere commenti in merito).

Combinando questo con il linguaggio tipografico di cui sopra si ottiene qualcosa che è relativamente vicino a C ++ 11 <=> in umanità (leggibilità e manutenibilità) senza dover fare qualcosa di troppo oscuro. E devo ammettere che è stato divertente, non avevo mai pensato di chiedere al compilatore se avevo a che fare con <=> s o no ...

Penso che Java enum sarebbe un buon modello da seguire. In sostanza, il modulo Java dovrebbe apparire così:

public enum Result {
    OK("OK"), CANCEL("Cancel");

    private final String name;

    Result(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

La cosa interessante dell'approccio Java è che OK e CANCEL sono istanze singleton immutabili di Result (con i metodi che vedi). Non è possibile creare ulteriori istanze di EnumSet. Dal momento che sono singoli, puoi confrontare per puntatore / riferimento --- molto utile. : -)

ETA: in Java, invece di fare maschere di bit a mano, invece usi un Set per specificare un set di bit (implementa l'interfaccia <=> e funziona come set --- ma implementato usando 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 in casi in cui non si definisce l'enum). Consente inoltre il controllo dell'intervallo di runtime.

Il rovescio della medaglia del mio approccio è che non impone a livello di programmazione l'accoppiamento tra l'enum e la classe helper, quindi devono essere aggiornati in parallelo. Funziona per me, ma YMMV.

Attualmente sto scrivendo la mia biblioteca enum typesafe su https://bitbucket.org/chopsii/typesafe- enumerazioni

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

Sentiti libero di provarlo e usarlo tu stesso, ma hanno alcuni problemi (si spera minori) di usabilità e probabilmente non sono affatto multipiattaforma.

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 averle trovate carenti, mi sono imbattuto in questo semplice approccio:

#include <iostream>
#include <boost/variant.hpp>

struct A_t {};
static const A_t A = A_t();
template <typename T>
bool isA(const T & x) { if(boost::get<A_t>(&x)) return true; return false; }

struct B_t {};
static const B_t B = B_t();
template <typename T>
bool isB(const T & x) { if(boost::get<B_t>(&x)) return true; return false; }

struct C_t {};
static const C_t C = C_t();
template <typename T>
bool isC(const T & x) { if(boost::get<C_t>(&x)) return true; return false; }

typedef boost::variant<A_t, B_t> AB;
typedef boost::variant<B_t, C_t> 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 trovare una macro per generare il boilerplate. (Fammi sapere se lo fai.)

A differenza di altri approcci questo è in realtà sicuro per i tipi e funziona con il vecchio C ++. Puoi persino creare tipi interessanti come boost::variant<int, A_t, B_t, boost::none>, ad esempio, per rappresentare un valore che potrebbe essere A, B, un numero intero o niente che è quasi livelli di sicurezza del tipo Haskell98.

Aspetti negativi di cui essere a conoscenza:

  • almeno con il vecchio boost - Sono su un sistema con boost 1.33 - sei limitato a 20 elementi nella tua variante; c'è comunque una soluzione alternativa
  • influenza il tempo di compilazione
  • messaggi di errore folli - ma questo è C ++ per te

Aggiornamento

Qui, per tua comodità, è la tua typesafe-enum " libreria " ;. Incolla questa intestazione:

#ifndef _TYPESAFE_ENUMS_H
#define _TYPESAFE_ENUMS_H
#include <string>
#include <boost/variant.hpp>

#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 <typename T> \
bool is##NAME(const T & x) { if(boost::get<NAME##_t>(&x)) return true; return false; } \


class toStr_visitor: public boost::static_visitor<std::string> {
public:
  template<typename T>
  std::string operator()(const T & a) const {
    return a.toStr();
  }
};

template<BOOST_VARIANT_ENUM_PARAMS(typename T)>
inline static
std::string toStr(const boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> & a) {
  return boost::apply_visitor(toStr_visitor(), a);
}

class toInt_visitor: public boost::static_visitor<int> {
public:
  template<typename T>
  int operator()(const T & a) const {
    return a.toInt();
  }
};

template<BOOST_VARIANT_ENUM_PARAMS(typename T)>
inline static
int toInt(const boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> & 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, nota che ora c'è una funzione toStr e una funzione toInt per soddisfare i requisiti dei PO di una semplice conversione in stringhe e ints. 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 (capacità di iterare su enumeratori): http://www.gamedev.net/reference/snippets/features/cppstringizing/

Il metodo descritto nell'articolo consente il supporto della conversione di stringhe per enumerazioni esistenti senza modificarne il codice. Se desideri solo il supporto per le nuove enumerazioni, preferirei Boost.Enum (menzionato sopra).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top