¿Debo usar #define, enum o const?


125

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 enumpierdo algo de espacio (enteros) y probablemente tenga que lanzar cuando quiero hacer una operación bit a bit. Con constcreo que la seguridad de tipos también pierden ya que una al azar uint8podrí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 #defines, y he usado espacios de nombres y plantillas en pocos espacios, por lo que tampoco están fuera de discusión.


"con enum pierdo algo de espacio (enteros)". No necesariamente. Consulte stackoverflow.com/questions/366017/… y stackoverflow.com/questions/1113855/… (y gcc's -fshort-enum . (Supongo que estas respuestas C siguen siendo ciertas en C ++.)
idbrii

@pydave En caso de que no esté seguro acerca de la compatibilidad de C y C ++, este enlace me parece muy útil, consulte por ejemplo enum david.tribble.com/text/cdiffs.htm#C99-enum-type
aka.nice

3
Este es un tema antiguo con altos votos, ¿hay alguna razón para no mencionar las clases de enumeración de C ++ 11 para esta situación problemática?
Brandin

Como nota, enum RecordType : uint8_tcombina el tipo de seguridad de enumcon el tamaño pequeño de uint8_t, aunque aún tendrá que proporcionar operadores bit a bit.
Justin Time - Restablece a Monica el

Respuestas:


88

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.

namespace RecordType {

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 ++.

enum TRecordType { xNew = 1, xDeleted = 2, xModified = 4, xExisting = 8,

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.

xInvalid = 16 };

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 typedefes solo un intvalor que 0xDEADBEEFpuede estar en su variable a través de variables no inicializadas o mal asignadas.

inline bool IsValidState( TRecordType v) {
    switch(v) { case xNew: case xDeleted: case xModified: case xExisting: return true; }
    return false;
}

 inline bool IsValidMask( TRecordType v) {
    return v >= xNew  && v < xInvalid ;
}

Agregue una usingdirectiva si desea usar el tipo con frecuencia.

using RecordType ::TRecordType ;

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.

void showRecords(TRecordType mask) {
    assert(RecordType::IsValidMask(mask));
    // do stuff;
}

void wombleRecord(TRecord rec, TRecordType state) {
    assert(RecordType::IsValidState(state));
    if (RecordType ::xNew) {
    // ...
} in runtime

TRecordType updateRecord(TRecord rec, TRecordType newstate) {
    assert(RecordType::IsValidState(newstate));
    //...
    if (! access_was_successful) return RecordType ::xInvalid;
    return newstate;
}

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.


1
En general, es una buena respuesta, pero la pregunta estipula que los indicadores se pueden combinar y que la función IsValidState () no permite que se combinen.
Jonathan Leffler

3
@Jonathan Leffler: desde mi punto de vista, creo que 'IsValidState' no debe hacer eso, 'IsValidMask' es.
João Portela

1
¿Se desea que IsValidMaskno permita seleccionar ninguno (es decir 0)?
Joachim Sauer

2
−1 La idea de la verificación del tipo de tiempo de ejecución es una abominación.
Saludos y hth. - Alf

54

Olvídate de lo definido

Contaminarán su código.

bitfields?

struct RecordFlag {
    unsigned isnew:1, isdeleted:1, ismodified:1, isexisting:1;
};

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.

Sin embargo, los miembros de bits en estructuras tienen inconvenientes prácticos. Primero, el orden de los bits en la memoria varía de un compilador a otro. Además, muchos compiladores populares generan código ineficiente para leer y escribir miembros de bits , y existen problemas de seguridad de subprocesos potencialmente graves relacionados con los campos de bits (especialmente en sistemas multiprocesador) debido al hecho de que la mayoría de las máquinas no pueden manipular conjuntos arbitrarios de bits en la memoria, pero en su lugar debe cargar y almacenar palabras completas. por ejemplo, lo siguiente no sería seguro para subprocesos, a pesar del uso de un mutex

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?

namespace RecordType {
    static const uint8 xNew = 1;
    static const uint8 xDeleted = 2;
    static const uint8 xModified = 4;
    static const uint8 xExisting = 8;
}

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:

namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

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.

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

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:

enum RecordType { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } ;

void doSomething(RecordType p_eMyEnum)
{
   if(p_eMyEnum == xNew)
   {
       // etc.
   }
}

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:

namespace RecordType {
   enum Value { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } ;
}

void doSomething(RecordType::Value p_eMyEnum)
{
   if(p_eMyEnum == RecordType::xNew)
   {
       // etc.
   }
}

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:

// Header.hpp
namespace RecordType {
    extern const uint8 xNew ;
    extern const uint8 xDeleted ;
    extern const uint8 xModified ;
    extern const uint8 xExisting ;
}

Y:

// Source.hpp
namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

Sin embargo, no podrá usar el interruptor en esas constantes. Así que al final, elige tu veneno ... :-p


55
¿Por qué crees que los campos de bits son lentos? ¿Realmente ha perfilado el código usándolo y otro método? Incluso si lo es, la claridad puede ser más importante que la velocidad, lo que simplifica un poco "nunca lo uses".
wnoise

"static const uint8 xNew;" solo es redundante porque en C ++ las variables con ámbito de espacio de nombres const tienen un enlace interno predeterminado. Eliminar "const" y tiene enlace externo. Además, "enum {...} RecordType;" declara una variable global llamada "RecordType" cuyo tipo es una enumeración anónima.
bk1e

OneByOne: En primer lugar, la razón principal fue que la ganancia (unos pocos bytes, si los hay) se overhsadowed por la pérdida (más lento de acceso, tanto de lectura y escritura) ...
paercebal

3
onebyone: Segundo, todo el código que produzco en el trabajo o en casa es inherentemente seguro para subprocesos. Es fácil de hacer: no global, no estático, no compartido entre hilos a menos que esté protegido por bloqueo. Usar este idioma rompería esta seguridad básica de hilos. ¿Y para qué? ¿ Quizás unos pocos bytes ? ... :-) ...
paercebal

Se agregó una referencia al artículo de Raymond Chen sobre los costos ocultos de bitfields.
paercebal

30

¿Has descartado std :: bitset? Conjuntos de banderas es para lo que sirve. Hacer

typedef std::bitset<4> RecordType;

luego

static const RecordType xNew(1);
static const RecordType xDeleted(2);
static const RecordType xModified(4);
static const RecordType xExisting(8);

Debido a que hay un montón de sobrecargas de operadores para bitset, ahora puede hacer

RecordType rt = whatever;      // unsigned long or RecordType expression
rt |= xNew;                    // set 
rt &= ~xDeleted;               // clear 
if ((rt & xModified) != 0) ... // test

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.


1
En realidad, no consideré bitset en absoluto. Sin embargo, no estoy seguro de que sea bueno. Con bitset, tengo que direccionar los bits como 1, 2, 3, 4, lo que haría que el código fuera menos legible, lo que significa que probablemente usaría una enumeración para 'nombrar' los bits. Sin embargo, podría ser un ahorrador de espacio. Gracias.
Milan Babuškov

Milan, no tienes que "nombrar" los bits usando una enumeración, solo puedes usar los bits predefinidos como se muestra arriba. Si desea activar el bit uno, en lugar de my_bitset.flip (1), debería hacer my_bitset | = xNew;
moswald

esto se dirige menos a usted y más al STL, pero: realmente tengo que preguntar: ¿por qué usaría bitsetesto? por lo general, se traduce en un longtipo 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, constexprcon cero almacenamiento)
underscore_d

[editar tiempo de espera] ... pero nunca entendí realmente la lógica de la bitsetclase, 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 '
underscore_d

"las uint8variables y los parámetros probablemente no usarán menos pila que ints" está mal. Si tiene una CPU con registros de 8 bits, intnecesita al menos 2 registros mientras que uint8_tsolo 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 ser uint8_tno uint8)
12431234123412341234123


5

Si es posible, NO use macros. No son demasiado admirados cuando se trata de C ++ moderno.


44
Cierto. Lo que odio de las macros es que no puedes entrar en ellas si están equivocadas.
Carl

Me imagino que es algo que podría arreglarse en el compilador.
celticminstrel

4

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.


4

Con define pierdo seguridad tipo

No necesariamente...

// signed defines
#define X_NEW      0x01u
#define X_NEW      (unsigned(0x01))  // if you find this more readable...

y con enum pierdo algo de espacio (enteros)

No necesariamente, pero debe ser explícito en los puntos de almacenamiento ...

struct X
{
    RecordType recordType : 4;  // use exactly 4 bits...
    RecordType recordType2 : 4;  // use another 4 bits, typically in the same byte
    // of course, the overall record size may still be padded...
};

y probablemente tenga que lanzar cuando quiero hacer una operación bit a bit.

Puede crear operadores para eliminar el dolor de eso:

RecordType operator|(RecordType lhs, RecordType rhs)
{
    return RecordType((unsigned)lhs | (unsigned)rhs);
}

Con const, creo que también pierdo la seguridad de tipos, ya que un uint8 aleatorio podría entrar por error.

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.

¿Hay alguna otra forma más limpia? / Si no, ¿qué usarías y por qué?

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

enum RecordType { New, Deleted, Modified, Existing };

showRecords([](RecordType r) { return r == New || r == Deleted; });

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.


1
+1 bien. Pero 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) );. Saludos
olibre

3

Incluso 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.


Puede especificar el tipo subyacente en C ++, a partir de la revisión del lenguaje C ++ 11. Hasta entonces, creo que era "al menos lo suficientemente grande como para almacenar y ser utilizado como un campo de bits para todos los enumeradores especificados, pero probablemente a intmenos que sea demasiado pequeño". [Si no especifica el tipo subyacente en C ++ 11, utiliza el comportamiento heredado. Por el contrario, enum classel tipo subyacente de C ++ 11 se establece por defecto explícitamente intsi no se especifica lo contrario.]
Justin Time - Restablece Monica el

3

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!


Parece excesivo para mi pequeña aplicación. Pero parece una buena solución.
Milan Babuškov

2

¿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:

struct RecordFlag {
    unsigned isnew:1, isdeleted:1, ismodified:1, isexisting:1;
};

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.


A veces como un todo, a veces como bandera. Y, también necesito probar si un determinado indicador está configurado (cuando lo paso como un todo).
Milan Babuškov

bueno, cuando está separado, solo pide un int. Cuando estén juntos, pasen la estructura.
wnoise

No será mejor El acceso a los campos de bits es más lento que cualquier otra cosa.
paercebal

De Verdad? ¿Crees que el compilador generará un código significativamente diferente para probar los campos de bits que el giro manual de bits? ¿Y que será significativamente más lento? ¿Por qué? La única cosa que no puede hacer tan fácilmente idiomáticamente es enmascarar varias banderas a la vez.
wnoise

Al ejecutar una prueba de lectura simple, obtengo 5.50-5.58 segundos para el enmascaramiento de bits frente a 5.45-5.59 para el acceso de campo de bits. Bastante indistinguible.
wnoise

2

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:

#define X_NEW      (1 << 0)
#define X_DELETED  (1 << 1)
#define X_MODIFIED (1 << 2)
#define X_EXISTING (1 << 3)

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.


1
Hay precedentes suficientes para eso, particularmente en las constantes para ioctl (). Sin embargo, prefiero usar constantes hexadecimales: 0x01, 0x02, 0x04, 0x08, 0x10, ...
Jonathan Leffler

2

Basado en KISS , alta cohesión y bajo acoplamiento , haga estas preguntas:

  • ¿Quién necesita saberlo? mi clase, mi biblioteca, otras clases, otras bibliotecas, terceros
  • ¿Qué nivel de abstracción debo proporcionar? ¿El consumidor entiende las operaciones de bits?
  • ¿Tendré que conectarme desde VB / C #, etc.?

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.


1
a) 5-6 clases. b) solo yo, es un proyecto de un solo hombre c) sin interacción
Milan Babuškov

2

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.


No, no Qt. En realidad, es un proyecto de wxWidgets.
Milan Babuškov

0

Prefiero ir con

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

Simplemente porque:

  1. Es más limpio y hace que el código sea legible y mantenible.
  2. Agrupa lógicamente las constantes.
  3. El tiempo del programador es más importante, a menos que su trabajo sea guardar esos 3 bytes.

Bueno, fácilmente podría tener un millón de instancias de la clase Record, por lo que podría ser importante. OTOH, eso es solo una diferencia entre 1 MB y 4 MB, así que tal vez no debería preocuparme.
Milan Babuškov

@Vivek: ¿Has considerado la limitación de ancho entero? En particular antes de C ++ 11.
user2672165

0

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


Desafortunadamente, la declaración tiene que estar en un archivo .h ya que se usa en todo el proyecto (usado por algunas clases 5-6).
Milan Babuškov
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.