Tratar enum
s como banderas funciona bien en C # a través del [Flags]
atributo, pero ¿cuál es la mejor manera de hacerlo en C ++?
Por ejemplo, me gustaría escribir:
enum AnimalFlags
{
HasClaws = 1,
CanFly =2,
EatsFish = 4,
Endangered = 8
};
seahawk.flags = CanFly | EatsFish | Endangered;
Sin embargo, obtengo errores de compilación con respecto a int
/ enum
conversiones. ¿Hay alguna manera mejor de expresar esto que solo un casting contundente? Preferiblemente, no quiero confiar en construcciones de bibliotecas de terceros como boost o Qt.
EDITAR: Como se indica en las respuestas, puedo evitar el error del compilador declarando seahawk.flags
como int
. Sin embargo, me gustaría tener algún mecanismo para hacer cumplir la seguridad de tipos, para que alguien no pueda escribir seahawk.flags = HasMaximizeButton
.
[Flags]
atributo funciona bien, es decir:[Flags] enum class FlagBits{ Ready = 1, ReadMode = 2, WriteMode = 4, EOF = 8, Disabled = 16};
Respuestas:
La forma "correcta" es definir operadores de bits para la enumeración, como:
Etc. resto de los operadores de bits. Modifique según sea necesario si el rango de enumeración excede el rango int.
fuente
AnimalFlags
está representado por la expresiónHasClaws | CanFly
? Esto no es para lo queenum
somos. Usa enteros y constantes.(HasClaws | CanFly)
".HasClaws
(= 1) comoCanFly
(= 2). Si, en cambio, solo asigna los valores del 1 al 4 directamente y obtiene un 3, podría ser un soloEatsFish
, o nuevamente una combinación deHasClaws
yCanFly
. Si su enumeración denota estados exclusivos solo entonces los valores consecutivos están bien, pero una combinación de banderas necesita que los valores sean exclusivos en bits.Nota (también un poco fuera de tema): otra forma de hacer banderas únicas se puede hacer usando un cambio de bit. Yo mismo, encuentro esto más fácil de leer.
Puede contener valores hasta un int, por lo que la mayoría de las veces, 32 indicadores se reflejan claramente en la cantidad de turno.
fuente
Para la gente perezosa como yo, aquí hay una solución para copiar y pegar:
fuente
using
donde sea apropiado, al igual querel_ops
.Tenga en cuenta que si está trabajando en un entorno Windows, hay una
DEFINE_ENUM_FLAG_OPERATORS
macro definida en winnt.h que hace el trabajo por usted. Entonces, en este caso, puedes hacer esto:fuente
¿De qué tipo es la variable seahawk.flags?
En C ++ estándar, las enumeraciones no son de tipo seguro. Son efectivamente enteros.
AnimalFlags NO debe ser el tipo de su variable. Su variable debe ser int y el error desaparecerá.
No es necesario poner valores hexadecimales como sugieren otras personas. No hace ninguna diferencia.
Los valores de enumeración SON de tipo int por defecto. Por lo tanto, seguramente puede combinarlos bit a bit o combinarlos y juntarlos y almacenar el resultado en un int.
El tipo enum es un subconjunto restringido de int cuyo valor es uno de sus valores enumerados. Por lo tanto, cuando crea un valor nuevo fuera de ese rango, no puede asignarlo sin convertirlo en una variable de su tipo de enumeración.
También puede cambiar los tipos de valores de enumeración si lo desea, pero esta pregunta no tiene sentido.
EDITAR: El póster decía que estaban preocupados por la seguridad del tipo y que no quieren un valor que no debería existir dentro del tipo int.
Pero sería inseguro poner un valor fuera del rango de AnimalFlags dentro de una variable de tipo AnimalFlags.
Hay una forma segura de verificar valores fuera de rango aunque dentro del tipo int ...
Lo anterior no le impide colocar una bandera inválida de una enumeración diferente que tiene el valor 1,2,4 u 8.
Si desea una seguridad de tipo absoluta, simplemente puede crear un std :: set y almacenar cada indicador dentro de él. No es eficiente en cuanto al espacio, pero es de tipo seguro y le brinda la misma capacidad que un bitflag int.
Nota de C ++ 0x: enumeraciones fuertemente tipadas
En C ++ 0x finalmente puede tener valores de tipo seguro enum ...
fuente
HasClaws | CanFly
es un tipo entero, pero el tipo deHasClaws
esAnimalFlags
, no un tipo entero.max(|emin| − K, |emax|)
e igual a(1u<<M) - 1
, dondeM
es un número entero no negativo ".int
.enum
técnicamente no es predeterminadoint
como su tipo subyacente (ya sea pre-C ++ 11 (IIRC) o post-C ++ 11 cuando no se especifica ningún tipo subyacente), aunque loenum class
hace . En su lugar, el tipo de defecto subyacentes a algo lo suficientemente grande como para representar a todos los encuestadores, con la regla fija única verdadera que es sólo más grande queint
si explícitamente necesita ser. Básicamente, el tipo subyacente se especifica como (parafraseado) "lo que sea que funcione, pero probablementeint
sea a menos que los enumeradores sean demasiado grandes paraint
".La respuesta actualmente aceptada por eidolon me parece demasiado peligrosa. El optimizador del compilador puede hacer suposiciones sobre posibles valores en la enumeración y puede obtener basura con valores no válidos. Y, por lo general, nadie quiere definir todas las permutaciones posibles en las enumeraciones de banderas.
Como dice Brian R. Bondy a continuación, si está usando C ++ 11 (que todos deberían usar, es tan bueno) ahora puede hacerlo más fácilmente con
enum class
:Esto asegura un tamaño estable y un rango de valores al especificar un tipo para la enumeración, inhibe la conversión automática de enumeraciones a ints, etc.
enum class
, y se utilizaconstexpr
para garantizar que el código para los operadores se alinee y, por lo tanto, sea tan rápido como los números regulares.Para personas atrapadas con dialectos de C ++ anteriores a 11
Si estuviera atascado con un compilador que no admite C ++ 11, iría envolviendo un tipo int en una clase que luego solo permite el uso de operadores bit a bit y los tipos de esa enumeración para establecer sus valores:
Puede definir esto más o menos como una enumeración normal + typedef:
Y el uso es similar también:
Y también puede anular el tipo subyacente para enumeraciones binarias estables (como C ++ 11
enum foo : type
) utilizando el segundo parámetro de plantilla, es decirtypedef SafeEnum<enum TFlags_,uint8_t> TFlags;
.Marqué la
operator bool
anulación con laexplicit
palabra clave de C ++ 11 para evitar que produzca conversiones int, ya que eso podría hacer que los conjuntos de indicadores terminen colapsados en 0 o 1 al escribirlos. Si no puede usar C ++ 11, deje esa sobrecarga y reescriba el primer condicional en el uso de ejemplo como(myFlags & EFlagTwo) == EFlagTwo
.fuente
std::underlying_type
lugar de codificar un tipo específico, o que el tipo subyacente se proporcione y use como un alias de tipo en lugar de directamente. De esa manera, los cambios en el tipo subyacente se propagarán automáticamente, en lugar de tener que hacerse manualmente.La forma más fácil de hacer esto como se muestra aquí , utilizando el conjunto de bits de clase de biblioteca estándar .
Para emular la función C # de una manera segura para los tipos, tendría que escribir un contenedor de plantillas alrededor del conjunto de bits, reemplazando los argumentos int con una enumeración dada como un parámetro de tipo para la plantilla. Algo como:
fuente
En mi opinión, ninguna de las respuestas hasta ahora es ideal. Para ser ideal, esperaría la solución:
==
,!=
,=
,&
,&=
,|
,|=
y~
los operadores en el sentido convencional (es decira & b
)if (a & b)...
La mayoría de las soluciones hasta ahora caen en los puntos 2 o 3. WebDancer es el cierre en mi opinión, pero falla en el punto 3 y debe repetirse para cada enumeración.
Mi solución propuesta es una versión generalizada de WebDancer que también aborda el punto 3:
Esto crea sobrecargas de los operadores necesarios pero usa SFINAE para limitarlos a tipos enumerados. Tenga en cuenta que, en aras de la brevedad, no he definido todos los operadores, pero el único que es diferente es el
&
. Los operadores son actualmente globales (es decir, se aplican a todos los tipos enumerados), pero esto podría reducirse colocando las sobrecargas en un espacio de nombres (lo que hago) o agregando condiciones SFINAE adicionales (quizás utilizando tipos subyacentes particulares o alias de tipo especialmente creados) ) Elunderlying_type_t
es una característica ++ 14 C pero parece estar bien apoyado y es fácil de emular para C ++ 11 con un sencillotemplate<typename T> using underlying_type_t = underlying_type<T>::type;
fuente
El estándar C ++ habla explícitamente de esto, consulte la sección "17.5.2.1.3 Tipos de máscara de bits":
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3485.pdf
Dada esta "plantilla" obtienes:
Y similar para los otros operadores. También tenga en cuenta el "constexpr", es necesario si desea que el compilador pueda ejecutar el tiempo de compilación de los operadores.
Si está utilizando C ++ / CLI y desea asignar a miembros de enumeración de clases de referencia, debe utilizar referencias de seguimiento en su lugar:
NOTA: Esta muestra no está completa; consulte la sección "17.5.2.1.3 Tipos de máscara de bits" para obtener un conjunto completo de operadores.
fuente
Me encontré haciendo la misma pregunta y se me ocurrió una solución genérica basada en C ++ 11, similar a la de soru:
La interfaz se puede mejorar al gusto. Entonces se puede usar así:
fuente
Si su compilador aún no admite enumeraciones fuertemente tipadas, puede consultar el siguiente artículo desde la fuente de c ++:
Del resumen:
fuente
Yo uso la siguiente macro:
Es similar a los mencionados anteriormente pero tiene varias mejoras:
int
)Es necesario incluir type_traits:
fuente
Me gustaría dar más detalles sobre la respuesta de Uliwitness , arreglando su código para C ++ 98 y usando el lenguaje Safe Bool , por falta de la
std::underlying_type<>
plantilla y laexplicit
palabra clave en las versiones de C ++ por debajo de C ++ 11.También lo modifiqué para que los valores de enumeración puedan ser secuenciales sin ninguna asignación explícita, para que pueda tener
Luego puede obtener el valor de las banderas sin procesar con
Aquí está el código.
fuente
Aquí hay una opción para las máscaras de bits si en realidad no utiliza los valores de enumeración individuales (por ejemplo, no necesita desactivarlos) ... y si no le preocupa mantener la compatibilidad binaria, es decir: usted no me importa dónde viven tus partes ... lo que probablemente estés. Además, es mejor que no te preocupes demasiado por el alcance y el control de acceso. Hmmm, las enumeraciones tienen algunas buenas propiedades para los campos de bits ... me pregunto si alguien ha intentado eso :)
Podemos ver que la vida es genial, tenemos nuestros valores discretos, y tenemos un buen int para & y | al contenido de nuestro corazón, que todavía tiene contexto de lo que significan sus partes Todo es consistente y predecible ... para mí ... siempre y cuando siga usando el compilador VC ++ de Microsoft con la Actualización 3 en Win10 x64 y no toque las banderas de mi compilador :)
A pesar de que todo es genial ... tenemos un contexto en cuanto al significado de las banderas ahora, ya que está en una unión con el campo de bits en el terrible mundo real donde su programa puede ser responsable de más de una tarea discreta que podría todavía accidentalmente (con bastante facilidad) rompen dos campos de banderas de diferentes uniones (por ejemplo, AnimalProperties y ObjectProperties, ya que ambos son ints), mezclando todos sus bits, que es un error horrible para rastrear ... y cómo sé Muchas personas en esta publicación no trabajan con máscaras de bits muy a menudo, ya que construirlas es fácil y mantenerlas es difícil.
Entonces, hace que la declaración de su sindicato sea privada para evitar el acceso directo a "Banderas", y tiene que agregar captadores / establecedores y sobrecargas del operador, luego crea una macro para todo eso, y está básicamente de vuelta a donde comenzó cuando intentó haz esto con una Enum.
Desafortunadamente, si desea que su código sea portátil, no creo que haya alguna forma de A) garantizar el diseño de bits o B) determinar el diseño de bits en el momento de la compilación (para que pueda rastrearlo y al menos corregir los cambios) versiones / plataformas, etc.) Offset en una estructura con campos de bits
En el tiempo de ejecución, puedes jugar trucos con los campos y XORingar las banderas para ver qué bits cambiaron, me suena bastante horrible, aunque los versos tienen una solución 100% consistente, independiente de la plataforma y completamente determinista, es decir: un ENUM.
TL; DR: No escuches a los que odian. C ++ no es inglés. El hecho de que la definición literal de una palabra clave abreviada heredada de C no se ajuste a su uso no significa que no deba usarla cuando la definición de C y C ++ de la palabra clave incluya absolutamente su caso de uso. También puede usar estructuras para modelar cosas que no sean estructuras y clases para cosas que no sean la escuela y la casta social. Puede usar flotante para valores que están conectados a tierra. Puede usar char para variables que no están sin grabar ni una persona en una novela, obra de teatro o película. Cualquier programador que vaya al diccionario para determinar el significado de una palabra clave antes de la especificación del idioma es un ... bueno, callaré allí.
Si desea que su código siga el modelo del lenguaje hablado, lo mejor sería escribir en Objective-C, que, por cierto, también utiliza enumeraciones en gran medida para los campos de bits.
fuente
Solo azúcar sintáctico. Sin metadatos adicionales.
Los operadores de marca en el tipo integral simplemente funcionan.
fuente
Actualmente no hay soporte de idioma para los indicadores de enumeración, las clases Meta podrían agregar esta característica inherentemente si alguna vez fuera parte del estándar c ++.
Mi solución sería crear funciones de plantilla instanciadas solo enum agregando soporte para operaciones bit a prueba de tipos para la clase enum usando su tipo subyacente:
Archivo: EnumClassBitwise.h
Por conveniencia y para reducir errores, es posible que desee ajustar sus operaciones de banderas de bits para enumeraciones y también para enteros:
Archivo: BitFlags.h
Posible uso:
fuente
@Xaqq ha proporcionado una forma realmente segura de escribir con seguridad para usar banderas de enumeración aquí por una
flag_set
clase.Publiqué el código en GitHub , el uso es el siguiente:
fuente
Estás confundiendo objetos y colecciones de objetos. Específicamente, está confundiendo banderas binarias con conjuntos de banderas binarias. Una solución adecuada se vería así:
fuente
Aquí está mi solución sin necesidad de sobrecargar o lanzar:
Creo que está bien, porque identificamos enumeraciones (no fuertemente tipadas) de todos modos.
Solo como una nota al margen (más larga), si
Se me ocurriría esto:
usando listas de inicializador de C ++ 11 y
enum class
.fuente
using Flags = unsigned long
dentro de un espacio de nombres o estructura que contenga los propios valores de marca como,/*static*/ const Flags XY = 0x01
etc.Como arriba (Kai) o haz lo siguiente. Realmente las enumeraciones son "Enumeraciones", lo que quieres hacer es tener un conjunto, por lo tanto, realmente deberías usar stl :: set
fuente
Tal vez como NS_OPTIONS de Objective-C.
fuente