Pregunta

Es de conocimiento común que las enumeraciones integradas en C++ no son seguras para tipos.Me preguntaba qué clases que implementan enumeraciones con seguridad de tipos se utilizan por ahí...Yo mismo uso la siguiente "bicicleta", pero es algo detallada y limitada:

tipossafeenum.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;

Suma:Creo que debería haber sido más específico sobre los requisitos.Intentaré resumirlos:

Prioridad 1:Establecer una variable de enumeración en un valor no válido debería ser imposible (un error en tiempo de compilación) sin excepciones.

Prioridad 2:Convertir un valor de enumeración a/desde un int debería ser posible con una única llamada explícita a una función/método.

Prioridad 3:Declaración y uso lo más compacto, elegante y conveniente posible

Prioridad 4:Conversión de valores de enumeración hacia y desde cadenas.

Prioridad 5:(Es bueno tenerlo) Posibilidad de iterar sobre los valores de enumeración.

¿Fue útil?

Solución

Actualmente estoy jugando con la propuesta Boost.Enum de Boost Vault (filename enum_rev4.6.zip). Aunque nunca se envió oficialmente para su inclusión en Boost, es utilizable tal cual. (Falta documentación pero está compensada por un código fuente claro y buenas pruebas).

Boost.Enum te permite declarar una enumeración como esta:

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")
)

Y haga que se expanda automáticamente a esto:

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

Satisface las cinco prioridades que usted enumera.

Otros consejos

Un buen método de compromiso es este:

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

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

No es de tipo seguro en el mismo sentido que su versión, pero el uso es mejor que las enumeraciones estándar, y aún puede aprovechar la conversión de enteros cuando la necesite.

Utilizo enumeraciones C ++ 0x typesafe . Utilizo algunas plantillas / macros de ayuda que proporcionan la funcionalidad de cadena desde / hacia.

enum class Result { Ok, Cancel};

Yo no. Demasiada sobrecarga para poco beneficio. Además, poder convertir enumeraciones a diferentes tipos de datos para la serialización es una herramienta muy útil. Nunca he visto una instancia donde un & Quot; Escriba safe & Quot; la enumeración valdría la sobrecarga y la complejidad donde C ++ ya ofrece una implementación lo suficientemente buena.

Mi opinión es que estás inventando un problema y luego colocando una solución en él. No veo la necesidad de hacer un marco elaborado para una enumeración de valores. Si está dedicado a que sus valores solo sean miembros de un determinado conjunto, podría hackear una variante de un tipo de datos de conjunto único.

Personalmente estoy usando una versión adaptada del modismo de enumeración typesafe.No proporciona los cinco "requisitos" que usted indicó en su edición, pero de todos modos estoy totalmente en desacuerdo con algunos de ellos.Por ejemplo, no veo cómo Prio#4 (conversión de valores a cadenas) tiene algo que ver con la seguridad de tipos.La mayoría de las veces, la representación de cadenas de valores individuales debe estar separada de la definición del tipo de todos modos (piense en i18n por una sencilla razón).Prio#5 (iteración, que es opcional) es una de las cosas más bonitas que me gustaría ver naturalmente sucediendo en enumeraciones, así que me entristeció que aparezca como "opcional" en su solicitud, pero parece que es mejor abordarlo a través de un sistema de iteración separado como begin/end funciones o un enum_iterator, lo que hace que funcionen perfectamente con STL y C++11 foreach.

OTOH, este simple modismo proporciona Prio#3 Prio#1 gracias al hecho de que en su mayoría solo envuelve enums con más información de tipo.Sin mencionar que es una solución muy simple que en su mayor parte no requiere encabezados de dependencia externos, por lo que es bastante fácil de transportar.También tiene la ventaja de realizar enumeraciones con un ámbito similar al de 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;

El único "agujero" que ofrece la solución es que no aborda el hecho de que no previene enums de diferentes tipos (o un enum y un int) se comparen directamente, porque cuando usa valores directamente fuerza la conversión implícita a int:

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

Pero hasta ahora he descubierto que estos problemas se pueden resolver simplemente ofreciendo una mejor comparación con el compilador; por ejemplo, proporcionando explícitamente un operador que compare dos parámetros diferentes. enum tipos, luego forzándolo a fallar:

// 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!");
}

Aunque hasta ahora no parece romper el código, y lo hace para abordar explícitamente el problema específico sin hacer nada más, no estoy seguro de que tal cosa sea una cosa ".debería" hacer (sospecho que interferirá con enums ya participan en operadores de conversión declarados en otros lugares;Me encantaría recibir comentarios sobre esto).

Combinar esto con el lenguaje de seguridad de tipos anterior da algo que está relativamente cerca de C++ 11. enum class en humanibilidad (legibilidad y mantenibilidad) sin tener que hacer nada demasiado oscuro.Y tengo que admitir que fue divertido hacerlo, nunca había pensado en hacerlo. preguntar el compilador si estuviera tratando con enums o no...

Creo que Java enum Sería un buen modelo a seguir.Básicamente, el formulario Java se vería así:

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

    private final String name;

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

    public String getName() {
        return name;
    }
}

Lo interesante del enfoque Java es que OK y CANCEL son instancias únicas e inmutables de Result (con los métodos que ves).No puede crear más instancias de Result.Como son singletons, puedes compararlos mediante puntero/referencia, lo cual es muy útil.:-)

Hora estimada de llegada:En Java, en lugar de hacer máscaras de bits a mano, se utiliza una EnumSet para especificar un conjunto de bits (implementa el Set interfaz, y funciona como conjuntos, pero implementado usando máscaras de bits).¡Mucho más legible que la manipulación de máscaras de bits escritas a mano!

Di una respuesta a esto aquí , sobre un tema diferente. Es un estilo de enfoque diferente que permite la mayor parte de la misma funcionalidad sin necesidad de modificar la definición de enumeración original (y, en consecuencia, permite el uso en casos en los que no define la enumeración). También permite la verificación del rango de tiempo de ejecución.

La desventaja de mi enfoque es que no aplica mediante programación el acoplamiento entre la enumeración y la clase auxiliar, por lo que deben actualizarse en paralelo. Funciona para mí, pero YMMV.

Actualmente estoy escribiendo mi propia biblioteca de enumeración typesafe en https://bitbucket.org/chopsii/typesafe- enumeraciones

No soy el desarrollador de C ++ con más experiencia, pero estoy escribiendo esto debido a las deficiencias de las enumeraciones de la bóveda BOOST.

Siéntase libre de revisarlo y usarlo usted mismo, pero tienen algunos problemas de usabilidad (con suerte menores) y probablemente no sean multiplataforma.

Por favor contribuya si lo desea. Esta es mi primera empresa de código abierto.

Usar boost::variant!

Después de probar muchas de las ideas anteriores y encontrar que faltaban, se me ocurrió este enfoque simple:

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

Probablemente puedas crear una macro para generar el texto estándar.(Deja me saber si lo haces.)

A diferencia de otros enfoques, este es realmente seguro para escribir y funciona con el antiguo C++.Incluso puedes crear tipos geniales como boost::variant<int, A_t, B_t, boost::none>, por ejemplo, para representar un valor que podría ser A, B, un número entero o nada que sea casi los niveles de seguridad de tipos de Haskell98.

Desventajas a tener en cuenta:

  • al menos con el impulso anterior (estoy en un sistema con el impulso 1.33), estás limitado a 20 elementos en tu variante;hay una solución alternativa sin embargo
  • afecta el tiempo de compilación
  • mensajes de error locos, pero eso es C++ para ti

Actualizar

Aquí, para su comodidad, está su "biblioteca" de enumeración typesafe.Pegue este encabezado:

#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

Y úsalo como:

ITEM(A, 0);
ITEM(B, 1);
ITEM(C, 2);

ENUM(A_t, B_t) AB;
ENUM(B_t, C_t) BC;

Fíjate que tienes que decir A_t en lugar de A en el ENUM macro que destruye parte de la magia.Oh bien.Además, observe que ahora hay un toStr función y un toInt función para cumplir con el requisito de OP de conversión simple a cadenas e ints.El requisito que no puedo entender es una forma de iterar sobre los elementos.Déjame saber si sabes cómo escribir algo así.

No estoy seguro si esta publicación es demasiado tarde, pero hay un artículo en GameDev.net que satisface todos menos el quinto punto (capacidad de iterar sobre los enumeradores): http://www.gamedev.net/reference/snippets/features/cppstringizing/

El método descrito en el artículo permite el soporte de conversión de cadenas para enumeraciones existentes sin cambiar su código. Sin embargo, si solo desea soporte para nuevas enumeraciones, iría con Boost.Enum (mencionado anteriormente).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top