Un enum X : int
(C #) o enum class X : int
(C ++ 11) es un tipo que tiene un campo interno oculto int
que puede contener cualquier valor. Además, una serie de constantes predefinidas de X
se definen en la enumeración. Es posible convertir la enumeración a su valor entero y viceversa. Todo esto es cierto tanto en C # como en C ++ 11.
En C #, las enumeraciones no solo se utilizan para mantener valores individuales, sino también para mantener combinaciones de marcas en bits, según la recomendación de Microsoft . Tales enumeraciones están (generalmente, pero no necesariamente) decoradas con el [Flags]
atributo. Para facilitar la vida de los desarrolladores, los operadores bit a bit (OR, AND, etc.) están sobrecargados para que pueda hacer fácilmente algo como esto (C #):
void M(NumericType flags);
M(NumericType.Sign | NumericType.ZeroPadding);
Soy un desarrollador experimentado de C #, pero he estado programando C ++ solo por un par de días y no conozco las convenciones de C ++. Tengo la intención de usar una enumeración de C ++ 11 de la misma manera que solía hacerlo en C #. En C ++ 11, los operadores bit a bit en las enumeraciones de ámbito no están sobrecargados, por lo que quería sobrecargarlos .
Esto solicitó un debate, y las opiniones parecen variar entre tres opciones:
Una variable del tipo enum se usa para mantener el campo de bits, similar a C #:
void M(NumericType flags); // With operator overloading: M(NumericType::Sign | NumericType::ZeroPadding); // Without operator overloading: M(static_cast<NumericType>(static_cast<int>(NumericType::Sign) | static_cast<int>(NumericType::ZeroPadding)));
Pero esto contrarrestaría la filosofía de enumeración fuertemente tipada de las enumeraciones de ámbito de C ++ 11.
Use un entero simple si desea almacenar una combinación de enumeraciones bit a bit:
void M(int flags); M(static_cast<int>(NumericType::Sign) | static_cast<int>(NumericType::ZeroPadding));
Pero esto reduciría todo a una
int
, dejándote sin idea de qué tipo se supone que debes poner en el método.Escriba una clase separada que sobrecargue a los operadores y mantenga las banderas bit a bit en un campo entero oculto:
class NumericTypeFlags { unsigned flags_; public: NumericTypeFlags () : flags_(0) {} NumericTypeFlags (NumericType t) : flags_(static_cast<unsigned>(t)) {} //...define BITWISE test/set operations }; void M(NumericTypeFlags flags); M(NumericType::Sign | NumericType::ZeroPadding);
( código completo por usuario315052 )
Pero entonces no tienes IntelliSense o cualquier soporte que te sugiera los posibles valores.
Sé que esta es una pregunta subjetiva , pero: ¿qué enfoque debo usar? ¿Qué enfoque, si lo hay, es el más ampliamente reconocido en C ++? ¿Qué enfoque utiliza cuando trata con campos de bits y por qué ?
Por supuesto, dado que los tres enfoques funcionan, busco razones fácticas y técnicas, convenciones generalmente aceptadas y no simplemente preferencias personales.
Por ejemplo, debido a mi experiencia en C #, tiendo a utilizar el enfoque 1 en C ++. Esto tiene el beneficio adicional de que mi entorno de desarrollo puede darme pistas sobre los posibles valores, y con operadores de enumeración sobrecargados, esto es fácil de escribir y comprender, y bastante limpio. Y la firma del método muestra claramente qué tipo de valor espera. Pero la mayoría de la gente aquí no está de acuerdo conmigo, probablemente por una buena razón.
fuente
enum E { A = 1, B = 2, C = 4, };
, el rango es0..7
(3 bits). Por lo tanto, el estándar C ++ garantiza explícitamente que # 1 siempre será una opción viable. [Específicamente, porenum class
defecto aenum class : int
menos que se especifique lo contrario, y por lo tanto siempre tiene un tipo subyacente fijo.])Respuestas:
La forma más simple es proporcionarle al operador sobrecargas. Estoy pensando en crear una macro para expandir las sobrecargas básicas por tipo.
(Tenga en cuenta que
type_traits
es un encabezado C ++ 11 ystd::underlying_type_t
es una característica de C ++ 14).fuente
static_cast<T>
para la entrada, pero el estilo C para el resultado aquí?SBJFrameDrag
se define en una clase y el|
operador se usa más tarde en las definiciones de la misma clase, ¿cómo definiría el operador de modo que se pueda usar dentro de la clase?Históricamente, siempre habría usado la antigua enumeración (de tipo débil) para nombrar las constantes de bit, y solo había usado la clase de almacenamiento explícitamente para almacenar el indicador resultante. Aquí, la responsabilidad recaería en mí para asegurarme de que mis enumeraciones encajan en el tipo de almacenamiento y para realizar un seguimiento de la asociación entre el campo y sus constantes relacionadas.
Me gusta la idea de enumeraciones fuertemente tipadas, pero no estoy realmente cómodo con la idea de que las variables de tipo enumerado pueden contener valores que no están entre las constantes de esa enumeración.
Por ejemplo, suponiendo que el bit a bit o se ha sobrecargado:
Para su tercera opción, necesita algo repetitivo para extraer el tipo de almacenamiento de la enumeración. Asumiendo que queremos forzar un tipo subyacente sin signo (también podemos manejar con signo, con un poco más de código):
Esto todavía no le da IntelliSense o autocompletado, pero la detección del tipo de almacenamiento es menos fea de lo que originalmente esperaba.
Ahora, encontré una alternativa: puede especificar el tipo de almacenamiento para una enumeración de tipo débil. Incluso tiene la misma sintaxis que en C #
Debido a que tiene un tipo débil y se convierte implícitamente a / desde int (o cualquier tipo de almacenamiento que elija), se siente menos extraño tener valores que no coincidan con las constantes enumeradas.
La desventaja es que esto se describe como "transicional" ...
NÓTESE BIEN. Esta variante agrega sus constantes enumeradas tanto al ámbito anidado como al delimitador, pero puede solucionar esto con un espacio de nombres:
fuente
Puede definir indicadores de enumeración de tipo seguro en C ++ 11 utilizando
std::enable_if
. Esta es una implementación rudimentaria que puede faltar algunas cosas:Tenga en cuentanumber_of_bits
que desafortunadamente el compilador no puede completarlo, ya que C ++ no tiene ninguna forma de hacer una introspección de los posibles valores de una enumeración.Editar: En realidad estoy corregido, es posible que el compilador lo complete
number_of_bits
por usted.Tenga en cuenta que esto puede manejar (muy poco eficientemente) un rango de valores de enumeración no continuo. Digamos que no es una buena idea usar lo anterior con una enumeración como esta o se producirá locura:
Pero a fin de cuentas, al final es una solución bastante útil. No necesita ningún bitfiddling del lado del usuario, es de tipo seguro y dentro de sus límites, tan eficiente como sea posible (me estoy inclinando fuertemente por la
std::bitset
calidad de implementación aquí;)
).fuente
yo
odioDetesto las macros en mi C ++ 14 tanto como el siguiente chico, pero he empezado a usar esto por todas partes, y también bastante liberalmente:Hacer uso tan simple como
Y, como dicen, la prueba está en el budín:
Siéntase libre de definir cualquiera de los operadores individuales como mejor le parezca, pero en mi opinión altamente sesgada, C / C ++ es para interactuar con conceptos y flujos de bajo nivel, y puede sacar estos operadores bit a bit de mis manos frías y muertas. y lucharé contra ti con todas las macros impías y hechizos que puedo conjurar para mantenerlos.
fuente
std::enable_if
constd::is_enum
restringir sus sobrecargas de operadores en abierto sólo se trabaja con tipos enumerados. También he agregado operadores de comparación (usandostd::underlying_type
) y el operador lógico no para cerrar aún más la brecha sin perder el tipeo fuerte. La única cosa que no puedo coincidir es la conversión implícita a bool, peroflags != 0
y!flags
son suficientes para mí.Por lo general, definiría un conjunto de valores enteros que corresponden a números binarios de conjunto de un solo bit, luego los sumaría. Esta es la forma en que los programadores de C generalmente lo hacen.
Entonces debería (usando el operador de desplazamiento de bits para establecer los valores, por ejemplo, 1 << 2 es lo mismo que binario 100)
etc.
En C ++ tiene más opciones, defina un nuevo tipo en lugar de int (use typedef ) y establezca valores similares a los anteriores; o definir un campo de bits o un vector de bools . Los últimos 2 son muy eficientes en cuanto al espacio y tienen mucho más sentido para lidiar con las banderas. Un campo de bits tiene la ventaja de proporcionarle verificación de tipos (y, por lo tanto, intellisense).
Yo diría (obviamente subjetivo) que un programador de C ++ debería usar un campo de bits para su problema, pero tiendo a ver mucho el enfoque #define utilizado por los programas de C en los programas de C ++.
Supongo que el campo de bits es el más cercano a la enumeración de C #, por qué C # intentó sobrecargar una enumeración para que sea un tipo de campo de bits es extraño: una enumeración realmente debería ser un tipo de "selección única".
fuente
0b0100
) para que el1 << n
formato sea obsoleto.Un breve ejemplo de enum-flags a continuación, se parece bastante a C #.
Sobre el enfoque, en mi opinión: menos código, menos errores, mejor código.
ENUM_FLAGS (T) es una macro, definida en enum_flags.h (menos de 100 líneas, de uso gratuito sin restricciones).
fuente
::type
allí. Corregido: paste.ubuntu.com/23884820Hay otra forma de desollar al gato:
En lugar de sobrecargar los operadores de bits, al menos algunos prefieren simplemente agregar un revestimiento de 4 para ayudarlo a evitar esa desagradable restricción de enumeraciones de ámbito:
Por supuesto, debe escribir la
ut_cast()
cosa cada vez, pero en el lado positivo, esto produce un código más legible, en el mismo sentido que el usostatic_cast<>()
, en comparación con la conversión de tipo implícito ooperator uint16_t()
tipo de cosas.Y seamos honestos aquí, usar el tipo
Foo
como en el código anterior tiene sus peligros:En otro lugar, alguien podría hacer un cambio de mayúscula a variable
foo
y no esperar que tenga más de un valor ...Por lo tanto, ensuciar el código
ut_cast()
ayuda a alertar a los lectores de que algo extraño está sucediendo.fuente