En un proyecto de C ++ en el que estoy trabajando, tengo un tipo de valor de indicador que puede tener cuatro valores. Esas cuatro banderas se pueden combinar. Las banderas describen los registros en la base de datos y pueden ser:
- nuevo record
- registro eliminado
- registro modificado
- registro existente
Ahora, para cada registro deseo mantener este atributo, para poder usar una enumeración:
enum { xNew, xDeleted, xModified, xExisting }
Sin embargo, en otros lugares del código, necesito seleccionar qué registros serán visibles para el usuario, por lo que me gustaría poder pasar eso como un solo parámetro, como:
showRecords(xNew | xDeleted);
Entonces, parece que tengo tres posibles enfoques:
#define X_NEW 0x01
#define X_DELETED 0x02
#define X_MODIFIED 0x04
#define X_EXISTING 0x08
o
typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;
o
namespace RecordType {
static const uint8 xNew = 1;
static const uint8 xDeleted = 2;
static const uint8 xModified = 4;
static const uint8 xExisting = 8;
}
Los requisitos de espacio son importantes (byte vs int) pero no son cruciales. Con define, pierdo la seguridad de tipo, y enum
pierdo algo de espacio (enteros) y probablemente tenga que lanzar cuando quiero hacer una operación bit a bit. Con const
creo que la seguridad de tipos también pierden ya que una al azar uint8
podría conseguir por error.
¿Hay alguna otra forma más limpia?
Si no, ¿qué usarías y por qué?
PD: El resto del código es bastante limpio y moderno C ++ sin #define
s, y he usado espacios de nombres y plantillas en pocos espacios, por lo que tampoco están fuera de discusión.
fuente
enum RecordType : uint8_t
combina el tipo de seguridad deenum
con el tamaño pequeño deuint8_t
, aunque aún tendrá que proporcionar operadores bit a bit.Respuestas:
Combine las estrategias para reducir las desventajas de un enfoque único. Trabajo en sistemas embebidos, por lo que la siguiente solución se basa en el hecho de que los operadores enteros y bit a bit son rápidos, con poca memoria y poco uso de flash.
Coloque la enumeración en un espacio de nombres para evitar que las constantes contaminen el espacio de nombres global.
Una enumeración declara y define un tiempo de compilación marcado tipeado. Utilice siempre la verificación de tipo de tiempo de compilación para asegurarse de que los argumentos y las variables tengan el tipo correcto. No hay necesidad de typedef en C ++.
Cree otro miembro para un estado no válido. Esto puede ser útil como código de error; por ejemplo, cuando desea devolver el estado pero la operación de E / S falla. También es útil para la depuración; úselo en listas de inicialización y destructores para saber si se debe usar el valor de la variable.
Considere que tiene dos propósitos para este tipo. Para rastrear el estado actual de un registro y crear una máscara para seleccionar registros en ciertos estados. Cree una función en línea para probar si el valor del tipo es válido para su propósito; como un marcador de estado frente a una máscara de estado. Esto detectará errores, ya que
typedef
es solo unint
valor que0xDEADBEEF
puede estar en su variable a través de variables no inicializadas o mal asignadas.Agregue una
using
directiva si desea usar el tipo con frecuencia.Las funciones de comprobación de valores son útiles en afirmaciones para atrapar valores incorrectos tan pronto como se utilizan. Cuanto más rápido atrapes un error cuando corres, menos daño puede hacer.
Aquí hay algunos ejemplos para ponerlo todo junto.
La única forma de garantizar la seguridad del valor correcto es utilizar una clase dedicada con sobrecargas del operador y eso se deja como ejercicio para otro lector.
fuente
IsValidMask
no permita seleccionar ninguno (es decir0
)?Olvídate de lo definido
Contaminarán su código.
bitfields?
Nunca uses eso . Le preocupa más la velocidad que economizar 4 ints. El uso de campos de bits es en realidad más lento que el acceso a cualquier otro tipo.
Fuente: http://en.wikipedia.org/wiki/Bit_field :
Y si necesita más razones para no usar bitfields, quizás Raymond Chen lo convencerá en su publicación The Old New Thing Post: El análisis de costo-beneficio de bitfields para una colección de booleanos en http://blogs.msdn.com/oldnewthing/ archivo / 2008/11/26 / 9143050.aspx
const int?
Ponerlos en un espacio de nombres es genial. Si se declaran en su CPP o archivo de encabezado, sus valores estarán en línea. Podrá usar el interruptor en esos valores, pero aumentará ligeramente el acoplamiento.
Ah, sí: elimine la palabra clave estática . static está en desuso en C ++ cuando se usa como lo hace, y si uint8 es un tipo buildin, no necesitará esto para declarar esto en un encabezado incluido por múltiples fuentes del mismo módulo. Al final, el código debería ser:
El problema de este enfoque es que su código conoce el valor de sus constantes, lo que aumenta ligeramente el acoplamiento.
enumeración
Lo mismo que const int, con un tipeo algo más fuerte.
Sin embargo, todavía están contaminando el espacio de nombres global. Por cierto ... Eliminar el typedef . Estás trabajando en C ++. Esos typedefs de enumeraciones y estructuras están contaminando el código más que cualquier otra cosa.
El resultado es un poco:
Como puede ver, su enumeración está contaminando el espacio de nombres global. Si coloca esta enumeración en un espacio de nombres, tendrá algo como:
int externo const?
Si desea disminuir el acoplamiento (es decir, poder ocultar los valores de las constantes y, por lo tanto, modificarlos como desee sin necesidad de una compilación completa), puede declarar los ints como externos en el encabezado y como constantes en el archivo CPP , como en el siguiente ejemplo:
Y:
Sin embargo, no podrá usar el interruptor en esas constantes. Así que al final, elige tu veneno ... :-p
fuente
¿Has descartado std :: bitset? Conjuntos de banderas es para lo que sirve. Hacer
luego
Debido a que hay un montón de sobrecargas de operadores para bitset, ahora puede hacer
O algo muy similar a eso: agradecería cualquier corrección ya que no he probado esto. También puede referirse a los bits por índice, pero generalmente es mejor definir solo un conjunto de constantes, y las constantes RecordType son probablemente más útiles.
Suponiendo que haya descartado bitset, voto por la enumeración .
No creo que lanzar las enumeraciones sea una desventaja grave: está bien, por lo que es un poco ruidoso, y asignar un valor fuera de rango a una enumeración es un comportamiento indefinido, por lo que teóricamente es posible dispararse en el pie con algún C ++ inusual implementaciones Pero si solo lo hace cuando sea necesario (que es cuando pasa de int a enum iirc), es un código perfectamente normal que la gente ha visto antes.
También tengo dudas sobre cualquier costo de espacio de la enumeración. Las variables y parámetros de uint8 probablemente no usarán menos pila que ints, por lo que solo importa el almacenamiento en clases. Hay algunos casos en los que ganará el empaquetado de varios bytes en una estructura (en cuyo caso puede lanzar enumeraciones dentro y fuera del almacenamiento uint8), pero normalmente el relleno anulará el beneficio de todos modos.
Por lo tanto, la enumeración no tiene desventajas en comparación con las demás, y como ventaja le brinda un poco de seguridad de tipos (no puede asignar algún valor entero aleatorio sin emitir explícitamente) y formas limpias de referirse a todo.
Por preferencia, también pondría el "= 2" en la enumeración, por cierto. No es necesario, pero un "principio de mínimo asombro" sugiere que las 4 definiciones deberían tener el mismo aspecto.
fuente
bitset
esto? por lo general, se traduce en unlong
tipo de integral (en mi implementación iirc; sí, qué desperdicio) o similar para cada elemento de todos modos, entonces, ¿por qué no simplemente usar integrales no ofuscadas? (o, hoy en día,constexpr
con cero almacenamiento)bitset
clase, aparte de lo que parece ser una corriente subyacente recurrente en las discusiones de 'ugh, debemos encubrir las desagradables raíces de bajo nivel del lenguaje 'uint8
variables y los parámetros probablemente no usarán menos pila queints
" está mal. Si tiene una CPU con registros de 8 bits,int
necesita al menos 2 registros mientras queuint8_t
solo necesita 1, por lo que necesitará más espacio de pila porque es más probable que se quede sin registros (que también es más lento y puede aumentar el tamaño del código ( dependiendo del conjunto de instrucciones)). (Usted tiene un tipo, debe seruint8_t
nouint8
)Aquí hay un par de artículos sobre const vs. macros vs. enums:
Constantes simbólicas
Enumeración Constantes frente a objetos constantes
Creo que debería evitar las macros, especialmente porque escribió que la mayor parte de su nuevo código está en C ++ moderno.
fuente
Si es posible, NO use macros. No son demasiado admirados cuando se trata de C ++ moderno.
fuente
Las enumeraciones serían más apropiadas ya que proporcionan "significado a los identificadores", así como seguridad de tipo. Puede decir claramente que "xDeleted" es de "RecordType" y que representa "tipo de registro" (¡guau!) Incluso después de años. Los concursos requerirían comentarios para eso, también requerirían subir y bajar el código.
fuente
No necesariamente...
No necesariamente, pero debe ser explícito en los puntos de almacenamiento ...
Puede crear operadores para eliminar el dolor de eso:
Lo mismo puede suceder con cualquiera de estos mecanismos: las comprobaciones de rango y valor son normalmente ortogonales para escribir con seguridad (aunque los tipos definidos por el usuario, es decir, sus propias clases, pueden imponer "invariantes" sobre sus datos). Con las enumeraciones, el compilador es libre de elegir un tipo más grande para alojar los valores, y una variable de enumeración no inicializada, corrupta o simplemente mal configurada podría terminar interpretando su patrón de bits como un número que no esperaría, comparando desigual a cualquiera de los identificadores de enumeración, cualquier combinación de ellos y 0.
Bueno, al final, el OR probado de confianza de estilo B de enumeraciones funciona bastante bien una vez que tiene campos de bits y operadores personalizados en la imagen. Puede mejorar aún más su robustez con algunas funciones de validación personalizadas y aserciones como en la respuesta de mat_geek; técnicas a menudo igualmente aplicables al manejo de cadenas, int, valores dobles, etc.
Se podría argumentar que esto es "más limpio":
Soy indiferente: los bits de datos se ajustan más pero el código crece significativamente ... depende de cuántos objetos tenga, y los lamdbas, por hermosos que sean, son aún más desordenados y difíciles de obtener que los OR bit a bit.
Por cierto / - el argumento sobre la seguridad de los hilos en mi opinión bastante débil - mejor recordado como una consideración de fondo en lugar de convertirse en una fuerza dominante en la toma de decisiones; compartir un mutex a través de los campos de bits es una práctica más probable, incluso si no se da cuenta de su empaque (los mutexes son miembros de datos relativamente voluminosos. Tengo que estar realmente preocupado por el rendimiento para considerar tener múltiples mutexes en los miembros de un objeto, y miraría cuidadosamente suficiente para notar que eran campos de bits). Cualquier tipo de tamaño de subpalabras podría tener el mismo problema (por ejemplo, a
uint8_t
). De todos modos, podría intentar operaciones de estilo de comparación y cambio atómico si está desesperado por una mayor concurrencia.fuente
operator|
debería convertirse a un tipo entero (unsigned int
) antes de la instrucción|
. De lo contrario,operator|
se llamará a sí mismo de forma recursiva y provocará un desbordamiento de la pila en tiempo de ejecución. Sugiero:return RecordType( unsigned(lhs) | unsigned(rhs) );
. SaludosIncluso si tiene que usar 4 bytes para almacenar una enumeración (no estoy tan familiarizado con C ++; sé que puede especificar el tipo subyacente en C #), todavía vale la pena: use enumeraciones.
En esta época de servidores con GB de memoria, cosas como 4 bytes vs. 1 byte de memoria a nivel de aplicación en general no importan. Por supuesto, si en su situación particular, el uso de la memoria es tan importante (y no puede hacer que C ++ use un byte para respaldar la enumeración), entonces puede considerar la ruta 'const estática'.
Al final del día, tiene que preguntarse, ¿vale la pena el éxito de mantenimiento del uso de 'constante estática' para los 3 bytes de ahorro de memoria para su estructura de datos?
Algo más a tener en cuenta: IIRC, en x86, las estructuras de datos están alineadas en 4 bytes, por lo que, a menos que tenga una serie de elementos de ancho de byte en su estructura de 'registro', es posible que en realidad no importe. Pruebe y asegúrese de que lo haga antes de hacer una compensación en la capacidad de mantenimiento por rendimiento / espacio.
fuente
int
menos que sea demasiado pequeño". [Si no especifica el tipo subyacente en C ++ 11, utiliza el comportamiento heredado. Por el contrario,enum class
el tipo subyacente de C ++ 11 se establece por defecto explícitamenteint
si no se especifica lo contrario.]Si desea la seguridad de tipo de las clases, con la conveniencia de la sintaxis de enumeración y la comprobación de bits, considere las etiquetas seguras en C ++ . He trabajado con el autor, y él es bastante inteligente.
Pero ten cuidado. ¡Al final, este paquete usa plantillas y macros!
fuente
¿Realmente necesita pasar los valores de la bandera como un todo conceptual, o va a tener mucho código por bandera? De cualquier manera, creo que tener esto como clase o estructura de campos de bits de 1 bit en realidad podría ser más claro:
Entonces su clase de registro podría tener una variable miembro de estructura RecordFlag, las funciones pueden tomar argumentos de tipo struct RecordFlag, etc. El compilador debe empaquetar los campos de bits juntos, ahorrando espacio.
fuente
Probablemente no usaría una enumeración para este tipo de cosas donde los valores se pueden combinar, más típicamente las enumeraciones son estados mutuamente excluyentes.
Pero cualquiera que sea el método que utilice, para dejar más claro que estos son valores que son bits que se pueden combinar, use esta sintaxis para los valores reales:
El uso de un desplazamiento hacia la izquierda ayuda a indicar que cada valor está destinado a ser un solo bit, es menos probable que más tarde alguien haga algo mal, como agregar un nuevo valor y asignarle un valor de 9.
fuente
Basado en KISS , alta cohesión y bajo acoplamiento , haga estas preguntas:
Hay un gran libro " Diseño de software C ++ a gran escala ", que promueve los tipos de base externamente, si puede evitar otro archivo de encabezado / dependencia de interfaz que debería intentar.
fuente
Si está utilizando Qt, debería buscar QFlags . La clase QFlags proporciona una forma segura de escribir tipos de combinaciones OR de valores de enumeración.
fuente
Prefiero ir con
Simplemente porque:
fuente
No es que me guste sobregeniar todo, pero a veces en estos casos puede valer la pena crear una clase (pequeña) para encapsular esta información. Si crea una clase RecordType, entonces podría tener funciones como:
nulo setDeleted ();
vacío clearDeleted ();
bool isDeleted ();
etc ... (o lo que convenga)
Podría validar combinaciones (en el caso de que no todas las combinaciones sean legales, por ejemplo, si 'nuevo' y 'eliminado' no pudieran establecerse al mismo tiempo). Si acaba de usar máscaras de bits, etc., entonces el código que establece el estado debe validarse, una clase también puede encapsular esa lógica.
La clase también puede darle la capacidad de adjuntar información de registro significativa a cada estado, puede agregar una función para devolver una representación de cadena del estado actual, etc. (o usar los operadores de transmisión '<<').
Por todo eso, si le preocupa el almacenamiento, aún podría tener la clase solo con un miembro de datos 'char', por lo que solo tome una pequeña cantidad de almacenamiento (suponiendo que no sea virtual). Por supuesto, dependiendo del hardware, etc., puede tener problemas de alineación.
Podría tener los valores de bits reales no visibles para el resto del 'mundo' si están en un espacio de nombres anónimo dentro del archivo cpp en lugar de en el archivo de encabezado.
Si encuentra que el código que usa enum / # define / bitmask, etc. tiene mucho código de 'soporte' para tratar combinaciones inválidas, registros, etc., entonces vale la pena considerar la encapsulación en una clase. Por supuesto, la mayoría de las veces los problemas simples mejoran con soluciones simples ...
fuente