[C++] ¿Debo usar #define, enum o const?


Answers

Olvida los define

Ellos contaminarán tu código.

bitfields?

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

Nunca uses eso . Le preocupa más la velocidad que la de economizar 4 ints. Usar campos de bit es en realidad más lento que el acceso a cualquier otro tipo.

Sin embargo, los miembros de bit en las estructuras tienen inconvenientes prácticos. En primer lugar, el orden de los bits en la memoria varía desde el compilador hasta el compilador. Además, muchos compiladores populares generan código ineficiente para leer y escribir miembros de bit , y hay problemas de seguridad de subproceso potencialmente graves relacionados con campos de bit (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 convenza en su 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/archive/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á utilizar estos 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 de compilación, no será necesario declarar esto en un encabezado incluido por varias 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.

enum

Lo mismo que const int, con un tipado 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 ... elimina el typedef . Estás trabajando en C ++. Esos tipos de enumeraciones de enums y structs 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 ve, su enumeración está contaminando el espacio de nombres global. Si pones esta enumeración en un espacio de nombres, tendrás 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.
   }
}

extern const int?

Si desea disminuir el acoplamiento (es decir, puede ocultar los valores de las constantes y modificarlas como desee sin necesidad de una recompilación completa), puede declarar las entradas como externas 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ás usar el interruptor de esas constantes. Entonces, al final, elige tu veneno ... :-p

Question

En un proyecto de C ++ en el que estoy trabajando, tengo un tipo de valor de indicador que puede tener cuatro valores. Esos cuatro indicadores se pueden combinar. Los indicadores 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, así podría usar una enumeración:

enum { xNew, xDeleted, xModified, xExisting }

Sin embargo, en otros lugares del código, necesito seleccionar qué registros deben ser visibles para el usuario, por lo que me gustaría poder pasar eso como un parámetro único, como:

showRecords(xNew | xDeleted);

Entonces, parece que tengo tres posibles candidatos:

#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 frente a int) pero no son cruciales. Con define I lose type safety, y con enum pierdo algo de espacio (enteros) y probablemente tenga que lanzar cuando quiero hacer una operación bit a bit. const creo que también pierdo seguridad de tipo ya que un uint8 azar podría entrar por error.

¿Hay alguna otra manera más limpia?

Si no, ¿qué usarías y por qué?

PD: El resto del código es bastante moderno y limpio, sin #define s, y he usado espacios de nombres y plantillas en pocos espacios, por lo que tampoco están fuera de discusión.







Los enum serían más apropiados ya que proporcionan "significado a los identificadores", así como tipo de seguridad. Puede decir claramente que "xDeleted" es de "RecordType" y que representa "tipo de registro" (¡guau!) Incluso después de años. Las ventajas requerirían comentarios para eso, también requerirían subir y bajar en el código.




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.



¿De verdad necesita pasar los valores de la bandera como un todo conceptual, o va a tener un montón de código por bandera? De cualquier manera, creo que tener esto como clase o estructura de bitfields de 1 bit 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 de estructura struct RecordFlag, las funciones pueden tomar argumentos de tipo struct RecordFlag, etc. El compilador debe empaquetar los bitfield juntos, ahorrando espacio.




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

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

Existe un gran libro " Diseño de software de C ++ a gran escala ", que promueve los tipos de base externamente, si puede evitar otra dependencia de archivo de cabecera / interfaz que debe intentar.




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 lo vale, use enumeraciones.

En esta época de servidores con GB de memoria, cosas como 4 bytes frente a 1 byte de memoria en el nivel de aplicación en general no importan. Por supuesto, si en su situación particular, el uso de 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 mantenimiento de usar 'static const' 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 un número de elementos de ancho de bytes en su estructura 'registro', puede que no importe. Pruebe y asegúrese de hacerlo antes de realizar un intercambio en la mantenibilidad del rendimiento / espacio.




Links