2010-09-30 20 views
6

Tengo un conjunto de banderas de bits que se usan en un programa que transfiero de C a C++.¿Por qué C++ admite la asignación hexadecimal, pero carece de asignación binaria? ¿Cuál es la mejor manera de almacenar banderas?

Para empezar ...

Las banderas en mi programa se definieron previamente como:

/* Define feature flags for this DCD file */ 
#define DCD_IS_CHARMM  0x01 
#define DCD_HAS_4DIMS  0x02 
#define DCD_HAS_EXTRA_BLOCK 0x04 

... Ahora he entendido que #defines para las constantes (en comparación con las constantes de clase, etc.) generalmente se consideran malas formas.

Esto plantea preguntas sobre cómo almacenar banderas de bits en C++ y por qué C++ no admite la asignación de texto binario a un int, como permite asignar números hexadecimales de esta manera (a través de "0x"). Estas preguntas se resumen al final de esta publicación.

pude ver una solución sencilla es simplemente crear constantes individuales:

namespace DCD { 
    const unsigned int IS_CHARMM = 1; 
    const unsigned int HAS_4DIMS = 2; 
    const unsigned int HAS_EXTRA_BLOCK = 4; 
}; 

Vamos a llamar a esta idea 1.

Otra idea que tenía era utilizar una enumeración entero:

namespace DCD { 
    enum e_Feature_Flags { 
     IS_CHARMM = 1, 
     HAS_4DIMS = 2, 
     HAS_EXTRA_BLOCK = 8 
    }; 
}; 

Pero una cosa que me molesta acerca de esto es que es menos intuitivo cuando se trata de valores más altos, parece ... es decir,

namespace DCD { 
    enum e_Feature_Flags { 
     IS_CHARMM = 1, 
     HAS_4DIMS = 2, 
     HAS_EXTRA_BLOCK = 8, 
     NEW_FLAG = 16, 
     NEW_FLAG_2 = 32, 
     NEW_FLAG_3 = 64, 
     NEW_FLAG_4 = 128 
    }; 
}; 

Vamos a llamar a esta opción enfoque 2.

estoy considerando el uso de solución de macro de Tom Torf:

#define B8(x) ((int) B8_(0x##x)) 

#define B8_(x) \ 
(((x) & 0xF0000000) >(28 - 7) \ 
| ((x) & 0x0F000000) >(24 - 6) \ 
| ((x) & 0x00F00000) >(20 - 5) \ 
| ((x) & 0x000F0000) >(16 - 4) \ 
| ((x) & 0x0000F000) >(12 - 3) \ 
| ((x) & 0x00000F00) >(8 - 2) \ 
| ((x) & 0x000000F0) >(4 - 1) \ 
| ((x) & 0x0000000F) >(0 - 0)) 

convertir en funciones inline, por ejemplo

#include <iostream> 
#include <string> 
.... 

/* TAKEN FROM THE C++ LITE FAQ [39.2]... */ 
class BadConversion : public std::runtime_error { 
public: 
    BadConversion(std::string const& s) 
    : std::runtime_error(s) 
    { } 
}; 

inline double convertToUI(std::string const& s) 
{ 
    std::istringstream i(s); 
    unsigned int x; 
    if (!(i >> x)) 
    throw BadConversion("convertToUI(\"" + s + "\")"); 
    return x; 
} 
/** END CODE **/ 

inline unsigned int B8(std::string x) { 
    unsigned int my_val = convertToUI(x.insert(0,"0x").c_str()); 
    return ((my_val) & 0xF0000000) >(28 - 7) | 
      ((my_val) & 0x0F000000) >(24 - 6) | 
      ((my_val) & 0x00F00000) >(20 - 5) | 
      ((my_val) & 0x000F0000) >(16 - 4) | 
      ((my_val) & 0x0000F000) >(12 - 3) | 
      ((my_val) & 0x00000F00) >(8 - 2) | 
      ((my_val) & 0x000000F0) >(4 - 1) | 
      ((my_val) & 0x0000000F) >(0 - 0); 
} 

namespace DCD { 
    enum e_Feature_Flags { 
     IS_CHARMM  = B8("00000001"), 
     HAS_4DIMS  = B8("00000010"), 
     HAS_EXTRA_BLOCK = B8("00000100"), 
     NEW_FLAG  = B8("00001000"), 
     NEW_FLAG_2  = B8("00010000"), 
     NEW_FLAG_3  = B8("00100000"), 
     NEW_FLAG_4  = B8("01000000") 
    }; 
}; 

¿Es esto una locura? ¿O parece más intuitivo? Vamos a llamar a esta elección 3.

Así que para recapitular, mis preguntas sobre-arqueo son:

1. ¿Por qué no C++ soportan una "0b" bandera valor, similar a "0 x"?
2. ¿Cuál es el mejor estilo para definir indicadores ...
i. Constantes envueltas en espacio de nombres.
ii. Espacio de nombres envuelto enum de entradas sin firmar asignadas directamente.
iii. Espacio de nombres envuelto enum de ints sin signo asignados mediante una cadena binaria legible.

¡Gracias de antemano! Y por favor no cierre este hilo como algo subjetivo, porque realmente quiero obtener ayuda sobre cuál es el mejor estilo y por qué C++ carece de la capacidad de asignación binaria incorporada.


EDITAR 1

Un poco de información adicional. Leeré un bitfield de 32 bits de un archivo y luego lo probaré con estos indicadores. Así que tenlo en cuenta cuando publiques sugerencias.

Respuesta

13

Antes de C++ 14, los literales binarios se habían discutido de vez en cuando a lo largo de los años, pero hasta donde sé, nadie había escrito una propuesta seria para incluirla en el estándar, por lo que nunca llegó a tener más allá de la etapa de hablar sobre eso.

Para C++ 14, alguien finalmente redactó una propuesta, y el comité la aceptó, por lo que si puede usar un compilador actual, la premisa básica de la pregunta es falsa: puede usar literales binarios, que tienen la forma 0b01010101.

En C++ 11, en lugar de agregar literales binarios directamente, agregaron un mecanismo mucho más general para permitir literales generales definidos por el usuario, que podría usar para soportar binarios o base 64 u otro tipo de cosas completamente . La idea básica es que especifiques un número (o cadena) literal seguido de un sufijo, y puedes definir una función que recibirá ese literal, y convertirlo a la forma que prefieras (y puedes mantener su estado como "constante" "también ...)

En cuanto a qué utilizar: si puede, los literales binarios integrados en C++ 14 o superior son la opción obvia. Si no se pueden utilizar, por lo general prefiero una variación de la opción 2: una enumeración con inicializadores en hexadecimal:

namespace DCD { 
    enum e_Feature_Flags { 
     IS_CHARMM = 0x1, 
     HAS_4DIMS = 0x2, 
     HAS_EXTRA_BLOCK = 0x8, 
     NEW_FLAG = 0x10, 
     NEW_FLAG_2 = 0x20, 
     NEW_FLAG_3 = 0x40, 
     NEW_FLAG_4 = 0x80 
    }; 
}; 

Otra posibilidad es algo así como:

#define bit(n) (1<<(n)) 

enum e_feature_flags = { 
    IS_CHARM = bit(0), 
    HAS_4DIMS = bit(1), 
    HAS_EXTRA_BLOCK = bit(3), 
    NEW_FLAG = bit(4), 
    NEW_FLAG_2 = bit(5), 
    NEW_FLAG_3 = bit(6), 
    NEW_FLAG_4 = bit(7) 
}; 
+0

Quiere decir 'inline unsigned int bit (n) {1 << (n);}' right? ;) –

+1

@Jason: No; los inicializadores del enumerador deben ser expresiones constantes; no puede llamar a una función en una expresión constante, por lo que tiene que ser una macro. –

+0

hrmm ... Supongo que es un buen momento para usar una macro? –

0

Supongo que la conclusión es que no es realmente necesario.

Si solo quieres usar binary para flags, el siguiente enfoque es como lo hago habitualmente. Después de que el original definir que nunca tenga que preocuparse de mirar "más sucios" múltiplos mayores de 2 como usted ha mencionado

int FLAG_1 = 1 
int FLAG_2 = 2 
int FLAG_3 = 4 
... 
int FLAG_N = 256 

se puede comprobar fácilmente con

if(someVariable & FLAG_3 == FLAG_3) { 
    // the flag is set 
} 

Y por cierto, Dependiendo de su compilador (estoy usando GNU GCC Compiler) puede admitir "0b"

nota Editado para responder a la pregunta.

+0

Sin embargo, esta es una extensión no compilable específica del compilador. No es parte de C++. –

+0

Usando g ++ obtengo el error 'sufijo no válido 'b0101' 'en constante entero' ... así que obviamente el uso de" 0b "en la asignación NO es seguro para compiladores cruzados y no es oficialmente compatible/una solución aceptable. –

+0

Interesante, antes de pensar que estaba en el estándar y que los compiladores habían elegido ignorarlo. No sabía que era al revés. –

7

Con la segunda opción, puede utilizar la desviación a la izquierda, que es quizás un poco menos "poco intuitivo:"

namespace DCD { 
    enum e_Feature_Flags { 
     IS_CHARMM =  1, 
     HAS_4DIMS =  (1 << 1), 
     HAS_EXTRA_BLOCK = (1 << 2), 
     NEW_FLAG =  (1 << 3), 
     NEW_FLAG_2 =  (1 << 4), 
     NEW_FLAG_3 =  (1 << 5), 
     NEW_FLAG_4 =  (1 << 6) 
    }; 
}; 
+0

Esta es la forma en que lo hago todo el tiempo en C ... – drahnr

+0

El enfoque de Jerry usando el 'bit (n)' es un poco más intuitivo leyendo el código visualmente, pero el tuyo no usa macros. Gah, solo puedo elegir una respuesta ... ambos ofrecieron buenas sugerencias. ¿Alguna idea de por qué nunca ha habido una propuesta para estandarizar "0b" o alguna forma similar de asignación? –

4

Así como una nota, Boost (como de costumbre) proporciona una implementation de esta idea.

+0

Si bien eso es realmente tentador, Boost no es C++ oficial y tengo ganas de arrastrarlo a mi proyecto de código introduciría posibles restricciones de dependencia/plataforma que no quiero tratar como programador de tiempo limitado que es autodidacta con C++. Además, mis compañeros de trabajo son aún más novatos en el mundo de la programación que yo (yo era CE, por lo menos tenía clases en Java y principios básicos con visual basic) así que tengo poca confianza en que podrían mantener mi código impulsado por impulso .... –

+4

No tengas miedo de las bibliotecas. Especialmente Boost, que es * muy * multiplataforma. –

2

¿Por qué no utilizar un bitfield struct?

struct preferences { 
     unsigned int likes_ice_cream : 1; 
     unsigned int plays_golf : 1; 
     unsigned int watches_tv : 1; 
     unsigned int reads_stackoverflow : 1; 
    }; 

struct preferences fred; 

fred.likes_ice_cream = 1; 
fred.plays_golf = 0; 
fred.watches_tv = 0; 
fred.reads_stackoverflow = 1; 

if (fred.likes_ice_cream == 1) 
    /* ... */ 
+0

Estoy leyendo en un valor entero de un archivo y voy a verificarlo contra las banderas, usando '&'. Imagino que su solución * podría * adaptarse a eso, pero parece menos intuitiva. –

+0

También se podría decir que está leyendo un bitfield de 32 bits de un archivo. –

1

¿Qué problema hay en hex para este caso de uso?

enum Flags { 
    FLAG_A = 0x00000001, 
    FLAG_B = 0x00000002, 
    FLAG_C = 0x00000004, 
    FLAG_D = 0x00000008, 
    FLAG_E = 0x00000010, 
    // ... 
}; 
2

GCC tiene una extensión por lo que es capaz de asignación binaria:

int n = 0b01010101; 

Edición: A partir del 14 C++, esto es ahora una parte oficial de la lengua.

+0

Sugeriría editar para notar que a partir de C++ 14, esta es la sintaxis oficial, no una extensión. –

+0

@underscore_d gracias, lo he hecho ahora. – Riot

Cuestiones relacionadas