design patterns - pattern - ¿Qué Typeum Enum en C++ estás usando?
geeksforgeeks design patterns (11)
Es de conocimiento común que las enumeraciones incorporadas en C ++ no son seguras para el tipo. Me preguntaba qué clases implementaron enums de tipo seguro se utilizan por ahí ... Yo mismo uso la siguiente "bicicleta", pero es un tanto detallado y limitado:
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;
Además: creo que debería haber sido más específico sobre los requisitos. Intentaré resumirlos:
Prioridad 1: Establecer una variable enum a un valor no válido debería ser imposible (un error en tiempo de compilación) sin excepciones.
Prioridad 2: la conversión de un valor enum a / desde un int debería ser posible con una única función explícita / llamada de método.
Prioridad 3: declaración y uso compactos, elegantes y convenientes como sea posible
Prioridad 4: Conversión de valores de enum desde y hacia cadenas.
Prioridad 5: (Es bueno tener) Posibilidad de iterar sobre valores enum.
Actualmente estoy escribiendo mi propia biblioteca enum de tipo seguro en https://bitbucket.org/chopsii/typesafe-enums
No soy el desarrollador de C ++ con más experiencia, pero escribo esto debido a las deficiencias de las enumeraciones de la bóveda BOOST.
No dude en echarle un vistazo y utilizarlos 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.
Actualmente estoy jugando con la propuesta de Boost.Enum de Boost Vault (nombre de archivo enum_rev4.6.zip
). Aunque nunca se presentó oficialmente para su inclusión en Boost, es utilizable tal cual. (La documentación es deficiente, pero se compensa con 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 haz 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 enumera.
Creo que Java enum
sería un buen modelo a seguir. Esencialmente, el formulario de 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 que es interesante sobre el enfoque de Java es que OK
y CANCEL
son instancias únicas e inmutables de Result
(con los métodos que usted ve). No puede crear más instancias de Result
. Ya que son singletons, puedes compararlos por puntero / referencia --- muy útil. :-)
ETA: en Java, en lugar de hacer máscaras de bits a mano, en su lugar utilizas un EnumSet
para especificar un conjunto de bits (implementa la interfaz Set
y funciona como conjuntos --- pero implementado usando máscaras de bits). ¡Mucho más legible que la manipulación de máscara de bits manuscrita!
Di una respuesta a esto here , sobre un tema diferente. Es un estilo de enfoque diferente que permite la mayor parte de la misma funcionalidad sin requerir modificaciones en la definición de enumeración original (y, en consecuencia, permite el uso en los casos en que no se 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 programáticamente el acoplamiento entre la clase enum y la clase auxiliar, por lo que deben actualizarse en paralelo. Funciona para mí, pero YMMV.
Mi opinión es que estás inventando un problema y luego ajustando una solución sobre é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, puede modificar una variante de un tipo de datos set único.
No estoy seguro de si esta publicación es demasiado tarde, pero hay un artículo en GameDev.net que satisface todos menos el 5to punto (capacidad de iterar sobre 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. Si solo quieres soporte para nuevas enumeraciones, iría con Boost.Enum (mencionado anteriormente).
Personalmente estoy usando una versión adaptada de la expresión enum de tipo seguro . No proporciona los cinco "requisitos" que ha indicado en su edición, pero estoy totalmente en desacuerdo con algunos de ellos de todos modos. Por ejemplo, no veo cómo Prio # 4 (conversión de valores en cadenas) tiene algo que ver con el tipo de seguridad. La mayoría de las veces, la representación de cadenas de valores individuales debería estar separada de la definición del tipo de todos modos (piense en i18n por una simple razón de por qué). Prio # 5 (iteratio, que es opcional) es una de las cosas más agradables que me gustaría ver que ocurra naturalmente en las enumeraciones, así que me entristece que aparezca como "opcional" en tu solicitud, pero parece que está mejor abordado a través de un sistema de iteración separado, como funciones de begin
/ end
o un enum_iterator, que hace que funcionen perfectamente con STL y C ++ 11 foreach.
OTOH esta simple expresión idiomática proporciona Prio # 3 Prio # 1 gracias al hecho de que solo envuelve enum
s con más información tipográfica. Sin mencionar que es una solución muy simple que en su mayoría no requiere encabezados de dependencia externos, por lo que es bastante fácil de llevar. También tiene la ventaja de hacer enumeraciones con alcance 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;
El único "agujero" que proporciona esta solución es que no aborda el hecho de que no impide que las enum
de diferentes tipos (o una enum
y un int) se comparen directamente, porque cuando se usan valores directamente, se fuerza el Conversión implícita a int
:
if (colors::salmon == fishes::salmon) { .../* Ooops! */... }
Pero hasta ahora he encontrado que tales problemas se pueden resolver simplemente ofreciendo una mejor comparación con el compilador, por ejemplo, proporcionando explícitamente un operador que compara dos tipos de enum
diferentes y luego lo fuerza 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 el momento no parece haber descifrado el código, y lo hace para tratar explícitamente el problema específico sin hacer otra cosa, no estoy seguro de que sea algo que " debería " hacer (sospecho que interferirá con enum
ya está tomando parte en operadores de conversión declarados en otro lugar, con mucho gusto recibiré comentarios sobre esto).
La combinación de esto con el tipo de lenguaje seguro proporciona algo que está relativamente cerca de la enum class
C ++ 11 en cuanto a la capacidad de ser humano (legibilidad y mantenibilidad) sin tener que hacer nada demasiado oscuro. Y tengo que admitir que fue divertido de hacer, nunca pensé en preguntarle al compilador si estaba tratando con enum
o no ...
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 seguro en cuanto a tu versión, pero el uso es mejor que las enumeraciones estándar, y aún puedes aprovechar la conversión entera cuando la necesites.
Utilice boost::variant
!
Después de probar muchas de las ideas anteriores y encontrarlas carentes, llegué a 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 se te ocurra una macro para generar la repetición. (Deja me saber si lo haces.)
A diferencia de otros enfoques, este es realmente seguro y funciona con C ++ antiguo. Incluso puede hacer 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 entero o nada que es casi Haskell98 niveles de seguridad tipo .
Desventajas a tener en cuenta:
- al menos con el antiguo impulso: estoy en un sistema con impulso 1.33: tiene un límite de 20 elementos en su variante; Sin embargo, hay una solución alternativa
- afecta el tiempo de compilación
- mensajes de error insanos, pero eso es C ++ para ti
Actualizar
Aquí, para su conveniencia, está su "biblioteca" typesafe-enum. Pega 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;
Observe que tiene que decir A_t
lugar de A
en la macro ENUM
que destruye parte de la magia. Oh bien. Además, observe que ahora hay una función toStr
y una función toInt
para cumplir con el requisito de OP de conversión simple a cadenas y entradas. El requisito que no puedo entender es una forma de iterar sobre los elementos. Avísame si sabes cómo escribir tal cosa.
Yo no. Demasiada sobrecarga para poco beneficio. Además, poder enumerar las diferentes tipos de datos para la serialización es una herramienta muy útil. Nunca he visto una instancia en la que una enumeración de "Tipo seguro" valga la sobrecarga y la complejidad donde C ++ ya ofrece una implementación lo suficientemente buena.
Yo uso C ++ 0x enum de tipo seguro . Utilizo algunas plantillas / macros de ayuda que proporcionan la funcionalidad de cadena a / desde.
enum class Result { Ok, Cancel};