¿Cómo usar enumeraciones como banderas en C ++?


187

Tratar enums como banderas funciona bien en C # a través del [Flags]atributo, pero ¿cuál es la mejor manera de hacerlo en C ++?

Por ejemplo, me gustaría escribir:

enum AnimalFlags
{
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8
};

seahawk.flags = CanFly | EatsFish | Endangered;

Sin embargo, obtengo errores de compilación con respecto a int/ enumconversiones. ¿Hay alguna manera mejor de expresar esto que solo un casting contundente? Preferiblemente, no quiero confiar en construcciones de bibliotecas de terceros como boost o Qt.

EDITAR: Como se indica en las respuestas, puedo evitar el error del compilador declarando seahawk.flagscomo int. Sin embargo, me gustaría tener algún mecanismo para hacer cumplir la seguridad de tipos, para que alguien no pueda escribir seahawk.flags = HasMaximizeButton.


Hasta donde yo sé en Visual C ++ 2013, el [Flags]atributo funciona bien, es decir:[Flags] enum class FlagBits{ Ready = 1, ReadMode = 2, WriteMode = 4, EOF = 8, Disabled = 16};
rivanov

@rivanov, No, no funciona con C ++ (2015 también). ¿Quiso decir C #?
Ajay

55
@rivanov, el atributo [Flags] solo funciona con .Net Framework en C ++ CLI, el C ++ nativo no admite dichos atributos.
Zoltan Tirinda

Respuestas:


251

La forma "correcta" es definir operadores de bits para la enumeración, como:

enum AnimalFlags
{
    HasClaws   = 1,
    CanFly     = 2,
    EatsFish   = 4,
    Endangered = 8
};

inline AnimalFlags operator|(AnimalFlags a, AnimalFlags b)
{
    return static_cast<AnimalFlags>(static_cast<int>(a) | static_cast<int>(b));
}

Etc. resto de los operadores de bits. Modifique según sea necesario si el rango de enumeración excede el rango int.


42
^ esto. La única pregunta es cómo automatizar / templar las definiciones del operador para que no tenga que definirlas constantemente cada vez que agrega una nueva enumeración.
eodabash

10
Además, ¿es válida la conversión de un int arbitrario al tipo enum, incluso si el valor int no corresponde a ninguno de los identificadores del enum?
Ingo Schalk-Schupp

8
Esto es una completa tontería. ¿Qué miembro de AnimalFlagsestá representado por la expresión HasClaws | CanFly? Esto no es para lo que enumsomos. Usa enteros y constantes.
Carreras de ligereza en órbita

26
@LightnessRacesinOrbit: Eso no es correcto. El dominio de un tipo de enumeración es el dominio de su tipo subyacente; es solo que a algunos se les ha dado un nombre. Y para responder a su pregunta: El miembro " (HasClaws | CanFly)".
Xeo

55
@MarcusJ: restringir sus valores a potencias de 2 le permite usar sus enumeraciones como indicadores de bits. Por lo tanto, si obtiene un 3, lo sabe tanto HasClaws(= 1) como CanFly(= 2). Si, en cambio, solo asigna los valores del 1 al 4 directamente y obtiene un 3, podría ser un solo EatsFish, o nuevamente una combinación de HasClawsy CanFly. Si su enumeración denota estados exclusivos solo entonces los valores consecutivos están bien, pero una combinación de banderas necesita que los valores sean exclusivos en bits.
Christian Severin

122

Nota (también un poco fuera de tema): otra forma de hacer banderas únicas se puede hacer usando un cambio de bit. Yo mismo, encuentro esto más fácil de leer.

enum Flags
{
    A = 1 << 0, // binary 0001
    B = 1 << 1, // binary 0010
    C = 1 << 2, // binary 0100
    D = 1 << 3, // binary 1000
};

Puede contener valores hasta un int, por lo que la mayoría de las veces, 32 indicadores se reflejan claramente en la cantidad de turno.


2
¿Podría eliminar la última coma (3,) y agregar dos puntos después} para que el código sea fácil de copiar y pegar? Gracias
Katu

44
¿Ninguna mención de hexidecimal? ¡Blasfemia!
Pharap

1
@Jamie, los cardenales siempre comienzan con 1, solo los ordinales pueden comenzar con 0 o 1, dependiendo de con quién esté hablando.
Michael

2
@Michael, eso es cierto! En una enumeración, generalmente reserva 0 para BLAH_NONE. :-) ¡Gracias por sacudir ese recuerdo!
Jamie

1
@Katu • la norma permite la coma superflua en la enumeración final. No me gusta, pero ya sé lo que Stroustrup me diría ... "¿No te gusta? Bueno, siéntete libre de crear tu propio idioma. Lo hice".
Eljay

55

Para la gente perezosa como yo, aquí hay una solución para copiar y pegar:

template<class T> inline T operator~ (T a) { return (T)~(int)a; }
template<class T> inline T operator| (T a, T b) { return (T)((int)a | (int)b); }
template<class T> inline T operator& (T a, T b) { return (T)((int)a & (int)b); }
template<class T> inline T operator^ (T a, T b) { return (T)((int)a ^ (int)b); }
template<class T> inline T& operator|= (T& a, T b) { return (T&)((int&)a |= (int)b); }
template<class T> inline T& operator&= (T& a, T b) { return (T&)((int&)a &= (int)b); }
template<class T> inline T& operator^= (T& a, T b) { return (T&)((int&)a ^= (int)b); }

23
+1 La pereza es una de las tres grandes virtudes de un programador: threevirtues.com
Pharap

10
Esta es una solución muy buena, solo tenga cuidado de que alegremente proporcionará operaciones bit a bit para cualquier tipo. Estoy usando algo similar, pero con la adición de rasgos que identifican los tipos a los que quiero que se aplique combinado con un poco de enable_if magic.
Rai

@Rai: siempre puedes ponerlo en un espacio de nombres y usingdonde sea apropiado, al igual que rel_ops.
Yakov Galka

1
@ybungalobill, pero aún tendrá el mismo problema con las operaciones que se aplican a cualquier tipo en el ámbito de uso, que presumiblemente coincidiría con la enumeración? Creo que los rasgos son probablemente necesarios.
Rai

19
No uses este código. Abre la puerta para que CUALQUIER clase sea operada por error. También el código está utilizando un reparto de estilo antiguo que no pasará por la compilación estricta de GCC shitalshah.com/p/… .
Shital Shah

44

Tenga en cuenta que si está trabajando en un entorno Windows, hay una DEFINE_ENUM_FLAG_OPERATORSmacro definida en winnt.h que hace el trabajo por usted. Entonces, en este caso, puedes hacer esto:

enum AnimalFlags
{
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8
};
DEFINE_ENUM_FLAG_OPERATORS(AnimalFlags)

seahawk.flags = CanFly | EatsFish | Endangered;

44

¿De qué tipo es la variable seahawk.flags?

En C ++ estándar, las enumeraciones no son de tipo seguro. Son efectivamente enteros.

AnimalFlags NO debe ser el tipo de su variable. Su variable debe ser int y el error desaparecerá.

No es necesario poner valores hexadecimales como sugieren otras personas. No hace ninguna diferencia.

Los valores de enumeración SON de tipo int por defecto. Por lo tanto, seguramente puede combinarlos bit a bit o combinarlos y juntarlos y almacenar el resultado en un int.

El tipo enum es un subconjunto restringido de int cuyo valor es uno de sus valores enumerados. Por lo tanto, cuando crea un valor nuevo fuera de ese rango, no puede asignarlo sin convertirlo en una variable de su tipo de enumeración.

También puede cambiar los tipos de valores de enumeración si lo desea, pero esta pregunta no tiene sentido.

EDITAR: El póster decía que estaban preocupados por la seguridad del tipo y que no quieren un valor que no debería existir dentro del tipo int.

Pero sería inseguro poner un valor fuera del rango de AnimalFlags dentro de una variable de tipo AnimalFlags.

Hay una forma segura de verificar valores fuera de rango aunque dentro del tipo int ...

int iFlags = HasClaws | CanFly;
//InvalidAnimalFlagMaxValue-1 gives you a value of all the bits 
// smaller than itself set to 1
//This check makes sure that no other bits are set.
assert(iFlags & ~(InvalidAnimalFlagMaxValue-1) == 0);

enum AnimalFlags {
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8,

    // put new enum values above here
    InvalidAnimalFlagMaxValue = 16
};

Lo anterior no le impide colocar una bandera inválida de una enumeración diferente que tiene el valor 1,2,4 u 8.

Si desea una seguridad de tipo absoluta, simplemente puede crear un std :: set y almacenar cada indicador dentro de él. No es eficiente en cuanto al espacio, pero es de tipo seguro y le brinda la misma capacidad que un bitflag int.

Nota de C ++ 0x: enumeraciones fuertemente tipadas

En C ++ 0x finalmente puede tener valores de tipo seguro enum ...

enum class AnimalFlags {
    CanFly = 2,
    HasClaws = 4
};

if(CanFly == 2) { }//Compiling error

44
Los valores de enumeración no son enteros, pero se convierten muy fácilmente en enteros. El tipo de HasClaws | CanFlyes un tipo entero, pero el tipo de HasClawses AnimalFlags, no un tipo entero.
Karu

1
Ah, pero ¿qué pasa si definimos el rango correcto de la enumeración para que no sean solo los valores de los indicadores individuales sino también sus combinaciones de bits? Entonces, la respuesta de eidolon es correcta, y mantiene que solo las combinaciones de la enumeración de bandera correcta se pueden pasar como ese tipo.
Scott

3
@Scott: Vale la pena señalar que el estándar C ++ define el rango válido de valores de una instancia de enumeración de esa manera. "para una enumeración donde emin es el enumerador más pequeño y emax es el más grande, los valores de la enumeración son los valores en el rango bmin a bmax, definidos de la siguiente manera: Sea K 1 para la representación del complemento de dos y 0 para los de uno representación de complemento o de magnitud de signo. bmax es el valor más pequeño mayor o igual que max(|emin| − K, |emax|)e igual a (1u<<M) - 1, donde Mes un número entero no negativo ".
Ben Voigt

Para aquellos que (como yo) solo quieren algo práctico que permita que los valores de enumeración sean manipulados bit a bit y no se vea demasiado feo con las plantillas y la conversión de tipos, esta es una buena solución; solo defina las variables para ser tipo int.
Eric Sokolowsky

También tenga en cuenta que en C ++, regular enumtécnicamente no es predeterminado intcomo su tipo subyacente (ya sea pre-C ++ 11 (IIRC) o post-C ++ 11 cuando no se especifica ningún tipo subyacente), aunque lo enum class hace . En su lugar, el tipo de defecto subyacentes a algo lo suficientemente grande como para representar a todos los encuestadores, con la regla fija única verdadera que es sólo más grande que intsi explícitamente necesita ser. Básicamente, el tipo subyacente se especifica como (parafraseado) "lo que sea que funcione, pero probablemente int sea ​​a menos que los enumeradores sean demasiado grandes para int".
Justin Time - Restablece a Monica el

26

La respuesta actualmente aceptada por eidolon me parece demasiado peligrosa. El optimizador del compilador puede hacer suposiciones sobre posibles valores en la enumeración y puede obtener basura con valores no válidos. Y, por lo general, nadie quiere definir todas las permutaciones posibles en las enumeraciones de banderas.

Como dice Brian R. Bondy a continuación, si está usando C ++ 11 (que todos deberían usar, es tan bueno) ahora puede hacerlo más fácilmente con enum class:

enum class ObjectType : uint32_t
{
    ANIMAL = (1 << 0),
    VEGETABLE = (1 << 1),
    MINERAL = (1 << 2)
};


constexpr enum ObjectType operator |( const enum ObjectType selfValue, const enum ObjectType inValue )
{
    return (enum ObjectType)(uint32_t(selfValue) | uint32_t(inValue));
}

// ... add more operators here. 

Esto asegura un tamaño estable y un rango de valores al especificar un tipo para la enumeración, inhibe la conversión automática de enumeraciones a ints, etc. enum class, y se utiliza constexprpara garantizar que el código para los operadores se alinee y, por lo tanto, sea tan rápido como los números regulares.

Para personas atrapadas con dialectos de C ++ anteriores a 11

Si estuviera atascado con un compilador que no admite C ++ 11, iría envolviendo un tipo int en una clase que luego solo permite el uso de operadores bit a bit y los tipos de esa enumeración para establecer sus valores:

template<class ENUM,class UNDERLYING=typename std::underlying_type<ENUM>::type>
class SafeEnum
{
public:
    SafeEnum() : mFlags(0) {}
    SafeEnum( ENUM singleFlag ) : mFlags(singleFlag) {}
    SafeEnum( const SafeEnum& original ) : mFlags(original.mFlags) {}

    SafeEnum&   operator |=( ENUM addValue )    { mFlags |= addValue; return *this; }
    SafeEnum    operator |( ENUM addValue )     { SafeEnum  result(*this); result |= addValue; return result; }
    SafeEnum&   operator &=( ENUM maskValue )   { mFlags &= maskValue; return *this; }
    SafeEnum    operator &( ENUM maskValue )    { SafeEnum  result(*this); result &= maskValue; return result; }
    SafeEnum    operator ~()    { SafeEnum  result(*this); result.mFlags = ~result.mFlags; return result; }
    explicit operator bool()                    { return mFlags != 0; }

protected:
    UNDERLYING  mFlags;
};

Puede definir esto más o menos como una enumeración normal + typedef:

enum TFlags_
{
    EFlagsNone  = 0,
    EFlagOne    = (1 << 0),
    EFlagTwo    = (1 << 1),
    EFlagThree  = (1 << 2),
    EFlagFour   = (1 << 3)
};

typedef SafeEnum<enum TFlags_>  TFlags;

Y el uso es similar también:

TFlags      myFlags;

myFlags |= EFlagTwo;
myFlags |= EFlagThree;

if( myFlags & EFlagTwo )
    std::cout << "flag 2 is set" << std::endl;
if( (myFlags & EFlagFour) == EFlagsNone )
    std::cout << "flag 4 is not set" << std::endl;

Y también puede anular el tipo subyacente para enumeraciones binarias estables (como C ++ 11 enum foo : type) utilizando el segundo parámetro de plantilla, es decir typedef SafeEnum<enum TFlags_,uint8_t> TFlags;.

Marqué la operator boolanulación con la explicitpalabra clave de C ++ 11 para evitar que produzca conversiones int, ya que eso podría hacer que los conjuntos de indicadores terminen colapsados ​​en 0 o 1 al escribirlos. Si no puede usar C ++ 11, deje esa sobrecarga y reescriba el primer condicional en el uso de ejemplo como (myFlags & EFlagTwo) == EFlagTwo.


Como nota, recomendaría que el operador de ejemplo definido al inicio use en std::underlying_typelugar de codificar un tipo específico, o que el tipo subyacente se proporcione y use como un alias de tipo en lugar de directamente. De esa manera, los cambios en el tipo subyacente se propagarán automáticamente, en lugar de tener que hacerse manualmente.
Justin Time - Restablece a Monica el

17

La forma más fácil de hacer esto como se muestra aquí , utilizando el conjunto de bits de clase de biblioteca estándar .

Para emular la función C # de una manera segura para los tipos, tendría que escribir un contenedor de plantillas alrededor del conjunto de bits, reemplazando los argumentos int con una enumeración dada como un parámetro de tipo para la plantilla. Algo como:

    template <class T, int N>
class FlagSet
{

    bitset<N> bits;

    FlagSet(T enumVal)
    {
        bits.set(enumVal);
    }

    // etc.
};

enum MyFlags
{
    FLAG_ONE,
    FLAG_TWO
};

FlagSet<MyFlags, 2> myFlag;

44
Mira esto para obtener un código más completo: codereview.stackexchange.com/questions/96146/…
Shital Shah

11

En mi opinión, ninguna de las respuestas hasta ahora es ideal. Para ser ideal, esperaría la solución:

  1. Apoyar la ==, !=, =, &, &=, |, |=y ~los operadores en el sentido convencional (es decir a & b)
  2. Sea seguro para los tipos, es decir, no permita que se asignen valores no enumerados, como literales o tipos enteros (excepto combinaciones bit a bit de valores enumerados) o permita que se asigne una variable enum a un tipo entero
  3. Permitir expresiones como if (a & b)...
  4. No requiere macros malvadas, características específicas de implementación u otros hacks

La mayoría de las soluciones hasta ahora caen en los puntos 2 o 3. WebDancer es el cierre en mi opinión, pero falla en el punto 3 y debe repetirse para cada enumeración.

Mi solución propuesta es una versión generalizada de WebDancer que también aborda el punto 3:

#include <cstdint>
#include <type_traits>

template<typename T = typename std::enable_if<std::is_enum<T>::value, T>::type>
class auto_bool
{
    T val_;
public:
    constexpr auto_bool(T val) : val_(val) {}
    constexpr operator T() const { return val_; }
    constexpr explicit operator bool() const
    {
        return static_cast<std::underlying_type_t<T>>(val_) != 0;
    }
};

template <typename T = typename std::enable_if<std::is_enum<T>::value, T>::type>
constexpr auto_bool<T> operator&(T lhs, T rhs)
{
    return static_cast<T>(
        static_cast<typename std::underlying_type<T>::type>(lhs) &
        static_cast<typename std::underlying_type<T>::type>(rhs));
}

template <typename T = typename std::enable_if<std::is_enum<T>::value, T>::type>
constexpr T operator|(T lhs, T rhs)
{
    return static_cast<T>(
        static_cast<typename std::underlying_type<T>::type>(lhs) |
        static_cast<typename std::underlying_type<T>::type>(rhs));
}

enum class AnimalFlags : uint8_t 
{
    HasClaws = 1,
    CanFly = 2,
    EatsFish = 4,
    Endangered = 8
};

enum class PlantFlags : uint8_t
{
    HasLeaves = 1,
    HasFlowers = 2,
    HasFruit = 4,
    HasThorns = 8
};

int main()
{
    AnimalFlags seahawk = AnimalFlags::CanFly;        // Compiles, as expected
    AnimalFlags lion = AnimalFlags::HasClaws;         // Compiles, as expected
    PlantFlags rose = PlantFlags::HasFlowers;         // Compiles, as expected
//  rose = 1;                                         // Won't compile, as expected
    if (seahawk != lion) {}                           // Compiles, as expected
//  if (seahawk == rose) {}                           // Won't compile, as expected
//  seahawk = PlantFlags::HasThorns;                  // Won't compile, as expected
    seahawk = seahawk | AnimalFlags::EatsFish;        // Compiles, as expected
    lion = AnimalFlags::HasClaws |                    // Compiles, as expected
           AnimalFlags::Endangered;
//  int eagle = AnimalFlags::CanFly |                 // Won't compile, as expected
//              AnimalFlags::HasClaws;
//  int has_claws = seahawk & AnimalFlags::CanFly;    // Won't compile, as expected
    if (seahawk & AnimalFlags::CanFly) {}             // Compiles, as expected
    seahawk = seahawk & AnimalFlags::CanFly;          // Compiles, as expected

    return 0;
}

Esto crea sobrecargas de los operadores necesarios pero usa SFINAE para limitarlos a tipos enumerados. Tenga en cuenta que, en aras de la brevedad, no he definido todos los operadores, pero el único que es diferente es el &. Los operadores son actualmente globales (es decir, se aplican a todos los tipos enumerados), pero esto podría reducirse colocando las sobrecargas en un espacio de nombres (lo que hago) o agregando condiciones SFINAE adicionales (quizás utilizando tipos subyacentes particulares o alias de tipo especialmente creados) ) El underlying_type_tes una característica ++ 14 C pero parece estar bien apoyado y es fácil de emular para C ++ 11 con un sencillotemplate<typename T> using underlying_type_t = underlying_type<T>::type;


Si bien su solución propuesta funciona muy bien, también presenta este patrón para enumeraciones que no deben ser tratadas como indicadores. Esa es probablemente la razón del uso de macros (malvadas) como DEFINE_ENUM_FLAG_OPERATORS de Microsoft.
WebDancer

@WebDancer, por supuesto que tienes razón, pero ya lo dije en mi respuesta. También sugerí dos formas de abordar el problema: ponerlo en un espacio de nombres o usar una condición SFINAE más restrictiva.
Trevor

Mi punto es que a menos que haga un espacio de nombres realmente estrecho (por ejemplo, el espacio de nombres AllMyFlagEnums) o tenga una condición SFINAE que de alguna manera seleccione solo unas pocas enumeraciones exactas, el código está roto en mi mente. En lugar de arriesgarme a esto, copio y pego una "plantilla textual" donde simplemente reemplazo el nombre de enumeración y, a veces, las macros "malvadas". Desearía que hubiera una mejor manera.
WebDancer

En primer lugar, solo causará un problema si en otra parte de su código necesita hacer una de las cosas que pretende detener, por ejemplo, asignar un literal, un entero o un elemento de otra enumeración. De lo contrario, la enumeración modificada se comporta como una enumeración regular, por ejemplo, los elementos no necesariamente tienen que ser potencias de dos y las operaciones de asignación, comparación y bit a bit funcionan de manera normal. Si realmente debe asignar literales o mezclar enumeraciones, aún puede emitir explícitamente, con la ventaja adicional de que su intención será más clara. Por lo tanto, es probable que no sea necesario reducir el alcance.
Trevor

En segundo lugar, si incluso necesita reducir el alcance, es posible que el espacio de nombres no necesite ser estrecho, aunque eso dependerá de lo que esté haciendo. Si está trabajando en una biblioteca, entonces quizás ya tenga su código que depende de las enumeraciones en un espacio de nombres, entonces el código de enumeración solo va en el mismo espacio de nombres. Si necesita el comportamiento de enumeración para una clase (quizás desee utilizar las enumeraciones como argumentos de método o variables miembro de la clase), coloque el código de enumeración en la clase para obtener el mismo efecto. En pocas palabras, no necesita ajustar un espacio de nombres solo alrededor de las enumeraciones, aunque podría hacerlo.
Trevor

8

El estándar C ++ habla explícitamente de esto, consulte la sección "17.5.2.1.3 Tipos de máscara de bits":

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3485.pdf

Dada esta "plantilla" obtienes:

enum AnimalFlags : unsigned int
{
    HasClaws = 1,
    CanFly = 2,
    EatsFish = 4,
    Endangered = 8
};

constexpr AnimalFlags operator|(AnimalFlags X, AnimalFlags Y) {
    return static_cast<AnimalFlags>(
        static_cast<unsigned int>(X) | static_cast<unsigned int>(Y));
}

AnimalFlags& operator|=(AnimalFlags& X, AnimalFlags Y) {
    X = X | Y; return X;
}

Y similar para los otros operadores. También tenga en cuenta el "constexpr", es necesario si desea que el compilador pueda ejecutar el tiempo de compilación de los operadores.

Si está utilizando C ++ / CLI y desea asignar a miembros de enumeración de clases de referencia, debe utilizar referencias de seguimiento en su lugar:

AnimalFlags% operator|=(AnimalFlags% X, AnimalFlags Y) {
    X = X | Y; return X;
}

NOTA: Esta muestra no está completa; consulte la sección "17.5.2.1.3 Tipos de máscara de bits" para obtener un conjunto completo de operadores.


6

Me encontré haciendo la misma pregunta y se me ocurrió una solución genérica basada en C ++ 11, similar a la de soru:

template <typename TENUM>
class FlagSet {

private:
    using TUNDER = typename std::underlying_type<TENUM>::type;
    std::bitset<std::numeric_limits<TUNDER>::max()> m_flags;

public:
    FlagSet() = default;

    template <typename... ARGS>
    FlagSet(TENUM f, ARGS... args) : FlagSet(args...)
    {   
        set(f);
    }   
    FlagSet& set(TENUM f)
    {   
        m_flags.set(static_cast<TUNDER>(f));
        return *this;
    }   
    bool test(TENUM f)
    {   
        return m_flags.test(static_cast<TUNDER>(f));
    }   
    FlagSet& operator|=(TENUM f)
    {   
        return set(f);
    }   
};

La interfaz se puede mejorar al gusto. Entonces se puede usar así:

FlagSet<Flags> flags{Flags::FLAG_A, Flags::FLAG_C};
flags |= Flags::FLAG_D;

2
Mire esto para obtener un código mejor y más completo: codereview.stackexchange.com/questions/96146/…
Shital Shah

55
Excepto por mi uso de numeric_limits, el código es casi el mismo. Supongo que es una forma común de tener una clase de enumeración segura. Yo diría que usar numeric_limits es mejor que poner un SENTINEL al final de cada enumeración.
Omair

1
Eso es un gran bitset!
Carreras ligeras en órbita el

(potencialmente ...)
ligereza corre en órbita el

5

Si su compilador aún no admite enumeraciones fuertemente tipadas, puede consultar el siguiente artículo desde la fuente de c ++:

Del resumen:

Este artículo presenta una solución al problema de restringir las operaciones de bits para
permitir solo operaciones seguras y legítimas, y convertir todas las manipulaciones de bits no válidas en errores en tiempo de compilación. Lo mejor de todo es que la sintaxis de las operaciones de bits permanece sin cambios, y el código que funciona con bits no necesita modificarse, excepto posiblemente para corregir errores que aún no se habían detectado.


5

Yo uso la siguiente macro:

#define ENUM_FLAG_OPERATORS(T)                                                                                                                                            \
    inline T operator~ (T a) { return static_cast<T>( ~static_cast<std::underlying_type<T>::type>(a) ); }                                                                       \
    inline T operator| (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) | static_cast<std::underlying_type<T>::type>(b) ); }                   \
    inline T operator& (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) & static_cast<std::underlying_type<T>::type>(b) ); }                   \
    inline T operator^ (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) ^ static_cast<std::underlying_type<T>::type>(b) ); }                   \
    inline T& operator|= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) |= static_cast<std::underlying_type<T>::type>(b) ); }   \
    inline T& operator&= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) &= static_cast<std::underlying_type<T>::type>(b) ); }   \
    inline T& operator^= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) ^= static_cast<std::underlying_type<T>::type>(b) ); }

Es similar a los mencionados anteriormente pero tiene varias mejoras:

  • Es de tipo seguro (no supone que el tipo subyacente sea un int)
  • No requiere especificar manualmente el tipo subyacente (a diferencia de la respuesta de @LunarEclipse)

Es necesario incluir type_traits:

#include <type_traits>

4

Me gustaría dar más detalles sobre la respuesta de Uliwitness , arreglando su código para C ++ 98 y usando el lenguaje Safe Bool , por falta de la std::underlying_type<>plantilla y la explicitpalabra clave en las versiones de C ++ por debajo de C ++ 11.

También lo modifiqué para que los valores de enumeración puedan ser secuenciales sin ninguna asignación explícita, para que pueda tener

enum AnimalFlags_
{
    HasClaws,
    CanFly,
    EatsFish,
    Endangered
};
typedef FlagsEnum<AnimalFlags_> AnimalFlags;

seahawk.flags = AnimalFlags() | CanFly | EatsFish | Endangered;

Luego puede obtener el valor de las banderas sin procesar con

seahawk.flags.value();

Aquí está el código.

template <typename EnumType, typename Underlying = int>
class FlagsEnum
{
    typedef Underlying FlagsEnum::* RestrictedBool;

public:
    FlagsEnum() : m_flags(Underlying()) {}

    FlagsEnum(EnumType singleFlag):
        m_flags(1 << singleFlag)
    {}

    FlagsEnum(const FlagsEnum& original):
        m_flags(original.m_flags)
    {}

    FlagsEnum& operator |=(const FlagsEnum& f) {
        m_flags |= f.m_flags;
        return *this;
    }

    FlagsEnum& operator &=(const FlagsEnum& f) {
        m_flags &= f.m_flags;
        return *this;
    }

    friend FlagsEnum operator |(const FlagsEnum& f1, const FlagsEnum& f2) {
        return FlagsEnum(f1) |= f2;
    }

    friend FlagsEnum operator &(const FlagsEnum& f1, const FlagsEnum& f2) {
        return FlagsEnum(f1) &= f2;
    }

    FlagsEnum operator ~() const {
        FlagsEnum result(*this);
        result.m_flags = ~result.m_flags;
        return result;
    }

    operator RestrictedBool() const {
        return m_flags ? &FlagsEnum::m_flags : 0;
    }

    Underlying value() const {
        return m_flags;
    }

protected:
    Underlying  m_flags;
};

3

Aquí hay una opción para las máscaras de bits si en realidad no utiliza los valores de enumeración individuales (por ejemplo, no necesita desactivarlos) ... y si no le preocupa mantener la compatibilidad binaria, es decir: usted no me importa dónde viven tus partes ... lo que probablemente estés. Además, es mejor que no te preocupes demasiado por el alcance y el control de acceso. Hmmm, las enumeraciones tienen algunas buenas propiedades para los campos de bits ... me pregunto si alguien ha intentado eso :)

struct AnimalProperties
{
    bool HasClaws : 1;
    bool CanFly : 1;
    bool EatsFish : 1;
    bool Endangered : 1;
};

union AnimalDescription
{
    AnimalProperties Properties;
    int Flags;
};

void TestUnionFlags()
{
    AnimalDescription propertiesA;
    propertiesA.Properties.CanFly = true;

    AnimalDescription propertiesB = propertiesA;
    propertiesB.Properties.EatsFish = true;

    if( propertiesA.Flags == propertiesB.Flags )
    {
        cout << "Life is terrible :(";
    }
    else
    {
        cout << "Life is great!";
    }

    AnimalDescription propertiesC = propertiesA;
    if( propertiesA.Flags == propertiesC.Flags )
    {
        cout << "Life is great!";
    }
    else
    {
        cout << "Life is terrible :(";
    }
}

Podemos ver que la vida es genial, tenemos nuestros valores discretos, y tenemos un buen int para & y | al contenido de nuestro corazón, que todavía tiene contexto de lo que significan sus partes Todo es consistente y predecible ... para mí ... siempre y cuando siga usando el compilador VC ++ de Microsoft con la Actualización 3 en Win10 x64 y no toque las banderas de mi compilador :)

A pesar de que todo es genial ... tenemos un contexto en cuanto al significado de las banderas ahora, ya que está en una unión con el campo de bits en el terrible mundo real donde su programa puede ser responsable de más de una tarea discreta que podría todavía accidentalmente (con bastante facilidad) rompen dos campos de banderas de diferentes uniones (por ejemplo, AnimalProperties y ObjectProperties, ya que ambos son ints), mezclando todos sus bits, que es un error horrible para rastrear ... y cómo sé Muchas personas en esta publicación no trabajan con máscaras de bits muy a menudo, ya que construirlas es fácil y mantenerlas es difícil.

class AnimalDefinition {
public:
    static AnimalDefinition *GetAnimalDefinition( AnimalFlags flags );   //A little too obvious for my taste... NEXT!
    static AnimalDefinition *GetAnimalDefinition( AnimalProperties properties );   //Oh I see how to use this! BORING, NEXT!
    static AnimalDefinition *GetAnimalDefinition( int flags ); //hmm, wish I could see how to construct a valid "flags" int without CrossFingers+Ctrl+Shift+F("Animal*"). Maybe just hard-code 16 or something?

    AnimalFlags animalFlags;  //Well this is *way* too hard to break unintentionally, screw this!
    int flags; //PERFECT! Nothing will ever go wrong here... 
    //wait, what values are used for this particular flags field? Is this AnimalFlags or ObjectFlags? Or is it RuntimePlatformFlags? Does it matter? Where's the documentation? 
    //Well luckily anyone in the code base and get confused and destroy the whole program! At least I don't need to static_cast anymore, phew!

    private:
    AnimalDescription m_description; //Oh I know what this is. All of the mystery and excitement of life has been stolen away :(
}

Entonces, hace que la declaración de su sindicato sea privada para evitar el acceso directo a "Banderas", y tiene que agregar captadores / establecedores y sobrecargas del operador, luego crea una macro para todo eso, y está básicamente de vuelta a donde comenzó cuando intentó haz esto con una Enum.

Desafortunadamente, si desea que su código sea portátil, no creo que haya alguna forma de A) garantizar el diseño de bits o B) determinar el diseño de bits en el momento de la compilación (para que pueda rastrearlo y al menos corregir los cambios) versiones / plataformas, etc.) Offset en una estructura con campos de bits

En el tiempo de ejecución, puedes jugar trucos con los campos y XORingar las banderas para ver qué bits cambiaron, me suena bastante horrible, aunque los versos tienen una solución 100% consistente, independiente de la plataforma y completamente determinista, es decir: un ENUM.

TL; DR: No escuches a los que odian. C ++ no es inglés. El hecho de que la definición literal de una palabra clave abreviada heredada de C no se ajuste a su uso no significa que no deba usarla cuando la definición de C y C ++ de la palabra clave incluya absolutamente su caso de uso. También puede usar estructuras para modelar cosas que no sean estructuras y clases para cosas que no sean la escuela y la casta social. Puede usar flotante para valores que están conectados a tierra. Puede usar char para variables que no están sin grabar ni una persona en una novela, obra de teatro o película. Cualquier programador que vaya al diccionario para determinar el significado de una palabra clave antes de la especificación del idioma es un ... bueno, callaré allí.

Si desea que su código siga el modelo del lenguaje hablado, lo mejor sería escribir en Objective-C, que, por cierto, también utiliza enumeraciones en gran medida para los campos de bits.


3

Solo azúcar sintáctico. Sin metadatos adicionales.

namespace UserRole // grupy
{ 
    constexpr uint8_t dea = 1;
    constexpr uint8_t red = 2;
    constexpr uint8_t stu = 4;
    constexpr uint8_t kie = 8;
    constexpr uint8_t adm = 16;
    constexpr uint8_t mas = 32;
}

Los operadores de marca en el tipo integral simplemente funcionan.


En mi humilde opinión, esta es la mejor respuesta. Limpio y simple, sintaxis de cliente fácil. Simplemente usaría "const int" en lugar de "constexpr uint8_t", pero el concepto es el mismo.
yoyo

(lo siento, "constexpr int")
yoyo

3

Actualmente no hay soporte de idioma para los indicadores de enumeración, las clases Meta podrían agregar esta característica inherentemente si alguna vez fuera parte del estándar c ++.

Mi solución sería crear funciones de plantilla instanciadas solo enum agregando soporte para operaciones bit a prueba de tipos para la clase enum usando su tipo subyacente:

Archivo: EnumClassBitwise.h

#pragma once
#ifndef _ENUM_CLASS_BITWISE_H_
#define _ENUM_CLASS_BITWISE_H_

#include <type_traits>

//unary ~operator    
template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum& operator~ (Enum& val)
{
    val = static_cast<Enum>(~static_cast<std::underlying_type_t<Enum>>(val));
    return val;
}

// & operator
template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum operator& (Enum lhs, Enum rhs)
{
    return static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) & static_cast<std::underlying_type_t<Enum>>(rhs));
}

// &= operator
template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum operator&= (Enum& lhs, Enum rhs)
{
    lhs = static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) & static_cast<std::underlying_type_t<Enum>>(rhs));
    return lhs;
}

//| operator

template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum operator| (Enum lhs, Enum rhs)
{
    return static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) | static_cast<std::underlying_type_t<Enum>>(rhs));
}
//|= operator

template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum& operator|= (Enum& lhs, Enum rhs)
{
    lhs = static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) | static_cast<std::underlying_type_t<Enum>>(rhs));
    return lhs;
}

#endif // _ENUM_CLASS_BITWISE_H_

Por conveniencia y para reducir errores, es posible que desee ajustar sus operaciones de banderas de bits para enumeraciones y también para enteros:

Archivo: BitFlags.h

#pragma once
#ifndef _BIT_FLAGS_H_
#define _BIT_FLAGS_H_

#include "EnumClassBitwise.h"

 template<typename T>
 class BitFlags
 {
 public:

     constexpr inline BitFlags() = default;
     constexpr inline BitFlags(T value) { mValue = value; }
     constexpr inline BitFlags operator| (T rhs) const { return mValue | rhs; }
     constexpr inline BitFlags operator& (T rhs) const { return mValue & rhs; }
     constexpr inline BitFlags operator~ () const { return ~mValue; }
     constexpr inline operator T() const { return mValue; }
     constexpr inline BitFlags& operator|=(T rhs) { mValue |= rhs; return *this; }
     constexpr inline BitFlags& operator&=(T rhs) { mValue &= rhs; return *this; }
     constexpr inline bool test(T rhs) const { return (mValue & rhs) == rhs; }
     constexpr inline void set(T rhs) { mValue |= rhs; }
     constexpr inline void clear(T rhs) { mValue &= ~rhs; }

 private:
     T mValue;
 };
#endif //#define _BIT_FLAGS_H_

Posible uso:

#include <cstdint>
#include <BitFlags.h>
void main()
{
    enum class Options : uint32_t
    { 
          NoOption = 0 << 0
        , Option1  = 1 << 0
        , Option2  = 1 << 1
        , Option3  = 1 << 2
        , Option4  = 1 << 3
    };

    const uint32_t Option1 = 1 << 0;
    const uint32_t Option2 = 1 << 1;
    const uint32_t Option3 = 1 << 2;
    const uint32_t Option4 = 1 << 3;

   //Enum BitFlags
    BitFlags<Options> optionsEnum(Options::NoOption);
    optionsEnum.set(Options::Option1 | Options::Option3);

   //Standard integer BitFlags
    BitFlags<uint32_t> optionsUint32(0);
    optionsUint32.set(Option1 | Option3); 

    return 0;
}

3

@Xaqq ha proporcionado una forma realmente segura de escribir con seguridad para usar banderas de enumeración aquí por una flag_setclase.

Publiqué el código en GitHub , el uso es el siguiente:

#include "flag_set.hpp"

enum class AnimalFlags : uint8_t {
    HAS_CLAWS,
    CAN_FLY,
    EATS_FISH,
    ENDANGERED,
    _
};

int main()
{
    flag_set<AnimalFlags> seahawkFlags(AnimalFlags::HAS_CLAWS
                                       | AnimalFlags::EATS_FISH
                                       | AnimalFlags::ENDANGERED);

    if (seahawkFlags & AnimalFlags::ENDANGERED)
        cout << "Seahawk is endangered";
}

2

Estás confundiendo objetos y colecciones de objetos. Específicamente, está confundiendo banderas binarias con conjuntos de banderas binarias. Una solución adecuada se vería así:

// These are individual flags
enum AnimalFlag // Flag, not Flags
{
    HasClaws = 0,
    CanFly,
    EatsFish,
    Endangered
};

class AnimalFlagSet
{
    int m_Flags;

  public:

    AnimalFlagSet() : m_Flags(0) { }

    void Set( AnimalFlag flag ) { m_Flags |= (1 << flag); }

    void Clear( AnimalFlag flag ) { m_Flags &= ~ (1 << flag); }

    bool Get( AnimalFlag flag ) const { return (m_Flags >> flag) & 1; }

};

2

Aquí está mi solución sin necesidad de sobrecargar o lanzar:

namespace EFoobar
{
    enum
    {
        FB_A    = 0x1,
        FB_B    = 0x2,
        FB_C    = 0x4,
    };
    typedef long Flags;
}

void Foobar(EFoobar::Flags flags)
{
    if (flags & EFoobar::FB_A)
        // do sth
        ;
    if (flags & EFoobar::FB_B)
        // do sth
        ;
}

void ExampleUsage()
{
    Foobar(EFoobar::FB_A | EFoobar::FB_B);
    EFoobar::Flags otherflags = 0;
    otherflags|= EFoobar::FB_B;
    otherflags&= ~EFoobar::FB_B;
    Foobar(otherflags);
}

Creo que está bien, porque identificamos enumeraciones (no fuertemente tipadas) de todos modos.

Solo como una nota al margen (más larga), si

  • querer usar enumeraciones fuertemente tipadas y
  • no necesito mucho juguetear con tus banderas
  • el rendimiento no es un problema

Se me ocurriría esto:

#include <set>

enum class EFoobarFlags
{
    FB_A = 1,
    FB_B,
    FB_C,
};

void Foobar(const std::set<EFoobarFlags>& flags)
{
    if (flags.find(EFoobarFlags::FB_A) != flags.end())
        // do sth
        ;
    if (flags.find(EFoobarFlags::FB_B) != flags.end())
        // do sth
        ;
}

void ExampleUsage()
{
    Foobar({EFoobarFlags::FB_A, EFoobarFlags::FB_B});
    std::set<EFoobarFlags> otherflags{};
    otherflags.insert(EFoobarFlags::FB_B);
    otherflags.erase(EFoobarFlags::FB_B);
    Foobar(otherflags);
}

usando listas de inicializador de C ++ 11 y enum class.


Por cierto, prefiero no recomendar enumeraciones para banderas en absoluto. Razón simple: las combinaciones de banderas no son elementos de la enumeración nuevamente. Entonces esto parece bastante inadecuado. Alternativamente, usaría un using Flags = unsigned longdentro de un espacio de nombres o estructura que contenga los propios valores de marca como, /*static*/ const Flags XY = 0x01etc.
yau

1

Como arriba (Kai) o haz lo siguiente. Realmente las enumeraciones son "Enumeraciones", lo que quieres hacer es tener un conjunto, por lo tanto, realmente deberías usar stl :: set

enum AnimalFlags
{
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8
};

int main(void)
{
    AnimalFlags seahawk;
    //seahawk= CanFly | EatsFish | Endangered;
    seahawk= static_cast<AnimalFlags>(CanFly | EatsFish | Endangered);
}

1

Tal vez como NS_OPTIONS de Objective-C.

#define ENUM(T1, T2) \
enum class T1 : T2; \
inline T1 operator~ (T1 a) { return (T1)~(int)a; } \
inline T1 operator| (T1 a, T1 b) { return static_cast<T1>((static_cast<T2>(a) | static_cast<T2>(b))); } \
inline T1 operator& (T1 a, T1 b) { return static_cast<T1>((static_cast<T2>(a) & static_cast<T2>(b))); } \
inline T1 operator^ (T1 a, T1 b) { return static_cast<T1>((static_cast<T2>(a) ^ static_cast<T2>(b))); } \
inline T1& operator|= (T1& a, T1 b) { return reinterpret_cast<T1&>((reinterpret_cast<T2&>(a) |= static_cast<T2>(b))); } \
inline T1& operator&= (T1& a, T1 b) { return reinterpret_cast<T1&>((reinterpret_cast<T2&>(a) &= static_cast<T2>(b))); } \
inline T1& operator^= (T1& a, T1 b) { return reinterpret_cast<T1&>((reinterpret_cast<T2&>(a) ^= static_cast<T2>(b))); } \
enum class T1 : T2

ENUM(Options, short) {
    FIRST  = 1 << 0,
    SECOND = 1 << 1,
    THIRD  = 1 << 2,
    FOURTH = 1 << 3
};

auto options = Options::FIRST | Options::SECOND;
options |= Options::THIRD;
if ((options & Options::SECOND) == Options::SECOND)
    cout << "Contains second option." << endl;
if ((options & Options::THIRD) == Options::THIRD)
    cout << "Contains third option." << endl;
return 0;

// Output:
// Contains second option. 
// Contains third option.

¿Puedes explicar por qué tu respuesta es la mejor? Hay varias otras respuestas que han respondido a esta pregunta, así que incluya información para diferenciar la suya.
trevorp
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.