Como usar enums como sinalizadores em C ++?


187

Tratar enums como sinalizadores funciona bem em C # por meio do[Flags] atributo, mas qual é a melhor maneira de fazer isso em C ++?

Por exemplo, eu gostaria de escrever:

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

seahawk.flags = CanFly | EatsFish | Endangered;

No entanto, eu recebo erros do compilador em relação a int/enum conversões. Existe uma maneira melhor de expressar isso do que apenas um casting sem corte? De preferência, não quero confiar em construções de bibliotecas de terceiros, como boost ou Qt.

EDIT: Como indicado nas respostas, posso evitar o erro do compilador declarando seahawk.flagscomo int. No entanto, eu gostaria de ter algum mecanismo para reforçar a segurança de tipos, para que alguém não possa escrever seahawk.flags = HasMaximizeButton.


Tanto quanto eu sei no Visual C ++ 2013, o [Flags]atributo funciona muito bem, ou seja:[Flags] enum class FlagBits{ Ready = 1, ReadMode = 2, WriteMode = 4, EOF = 8, Disabled = 16};
rivanov

@rivanov, Não, ele não funciona com C ++ (2015 também). Você quis dizer C #?
Ajay

5
@rivanov, O atributo [Flags] funciona apenas com o .Net Framework no C ++ CLI, o C ++ nativo não suporta esses atributos.
Zoltan Tirinda

Respostas:


251

A maneira "correta" é definir operadores de bits para a enumeração, 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 dos operadores de bits. Modifique conforme necessário se o intervalo de enum exceder o intervalo int.


42
^ isso. A única questão é como automatizar / modelar as definições do operador para que você não precise defini-las constantemente toda vez que adicionar uma nova enumeração.
EDabash # 03

10
Além disso, o elenco de um int arbitrário de volta para o tipo de enum é válido, mesmo que o valor int não corresponda a nenhum dos identificadores do enum?
Ingo Schalk-Schupp

8
Isso é um absurdo completo. Qual membro de AnimalFlagsé representado pela expressão HasClaws | CanFly? Não é para isso que enumservem. Use números inteiros e constantes.
Lightness Races em órbita

26
@ LightnessRacesinOrbit: Isso não está correto. O domínio de um tipo de enum é o domínio de seu tipo subjacente - é apenas que alguns receberam um nome. E para responder sua pergunta: O membro " (HasClaws | CanFly)".
Xeo 27/03

5
@ MarcusJ: restringir seus valores a potências de 2 permite que você use suas enumerações como bit-flags. Portanto, se você obtiver um 3, você o conhece HasClaws(= 1) e CanFly(= 2). Se, em vez disso, você apenas atribuir os valores de 1 a 4 e conseguir um 3, pode ser um único EatsFish, ou novamente uma combinação de HasClawse CanFly. Se sua enumeração indicar apenas estados exclusivos, os valores consecutivos serão bons, mas uma combinação de sinalizadores precisará que os valores sejam exclusivos de bits.
Christian Severin

122

Nota (também um pouco fora do tópico): Outra maneira de criar sinalizadores exclusivos pode ser feita usando uma mudança de bits. Eu próprio acho isso mais fácil de ler.

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

Ele pode manter valores até um int, ou seja, na maioria das vezes, 32 sinalizadores, o que é claramente refletido no valor do turno.


2
Você poderia excluir a última vírgula (3) e adicionar dois pontos depois} para facilitar a cópia e a colagem do código? Obrigado
Katu

4
Nenhuma menção de hexidecimal? Blasfêmia!
Pharap

1
@ Jamie, os cardeais sempre começam com 1, apenas os ordinais podem começar com 0 ou 1, dependendo de com quem você está falando.
Michael

2
@ Michael, isso é verdade! Em uma enumeração, você costuma reservar 0 para BLAH_NONE. :-) Obrigado por disfarçar essa memória!
Jamie

1
@Katu • a vírgula supérflua na enumeração final é permitida pelo padrão. Não gosto, mas já sei o que Stroustrup me diria ... "Você não gosta? Sinta-se à vontade para criar seu próprio idioma. Gostei."
Eljay

55

Para pessoas preguiçosas como eu, aqui está a solução para copiar e colar:

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 A preguiça é um dos três grandes virtudes de um programador: threevirtues.com
Pharap

10
Esta é uma solução muito agradável, apenas tenha cuidado para fornecer alegremente operações bit a bit para qualquer tipo. Estou usando algo semelhante, mas com a adição de características, identificamos os tipos aos quais quero aplicar, combinados com um pouco de enable_if mágica.
Rai

@ Rai: você sempre pode colocá-lo em um espaço para nome e using, quando apropriado, assim como rel_ops.
Yakov Galka

1
@ybungalobill, mas você ainda terá o mesmo problema com as operações aplicáveis ​​a qualquer tipo no escopo do using, o que provavelmente corresponderia ao enum? Eu acho que as características são provavelmente necessárias.
Rai

19
Não use esse código. Abre a porta para QUALQUER classe ser operada por engano. Além disso, o código está usando uma conversão de estilo antigo que não passará pela compilação estrita do GCC shitalshah.com/p/… .
Shital Shah

44

Observe que se você estiver trabalhando no ambiente Windows, há uma DEFINE_ENUM_FLAG_OPERATORSmacro definida em winnt.h que faz o trabalho para você. Portanto, neste caso, você pode fazer o seguinte:

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

seahawk.flags = CanFly | EatsFish | Endangered;

44

Que tipo é a variável seahawk.flags?

No C ++ padrão, as enumerações não são de tipo seguro. Eles são efetivamente inteiros.

AnimalFlags NÃO deve ser o tipo da sua variável. Sua variável deve ser int e o erro desaparecerá.

Não é necessário colocar valores hexadecimais como algumas pessoas sugeriram. Não faz diferença.

Os valores da enumeração SÃO do tipo int por padrão. Portanto, você pode certamente OR bit a bit OU combiná-los, reuni-los e armazenar o resultado em um int.

O tipo enum é um subconjunto restrito de int cujo valor é um de seus valores enumerados. Portanto, quando você cria um novo valor fora desse intervalo, não pode atribuí-lo sem converter para uma variável do seu tipo de enumeração.

Você também pode alterar os tipos de valor de enumeração, se desejar, mas não há sentido para esta pergunta.

EDIT: O pôster disse que eles estavam preocupados com a segurança do tipo e não querem um valor que não deveria existir dentro do tipo int.

Mas seria inseguro colocar um valor fora do intervalo do AnimalFlags dentro de uma variável do tipo AnimalFlags.

Existe uma maneira segura de verificar valores fora do intervalo, embora dentro do 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
};

O exposto acima não impede que você coloque um sinalizador inválido de uma enumeração diferente que tenha o valor 1,2,4 ou 8.

Se você quer segurança absoluta, basta criar um std :: set e armazenar cada sinalizador dentro dele. Não é eficiente em termos de espaço, mas é do tipo seguro e oferece a mesma capacidade que um int de sinal de bits.

Nota do C ++ 0x: enumerações fortemente tipadas

No C ++ 0x, você pode finalmente ter valores de enumeração de tipo seguros ....

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

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

4
Os valores da enumeração não são números inteiros, mas são facilmente convertidos em números inteiros. O tipo de HasClaws | CanFlyé algum tipo inteiro, mas o tipo de HasClawsé AnimalFlags, não um tipo inteiro.
23412 Karu

1
Ah, mas e se definirmos o intervalo correto da enumeração não apenas para os valores individuais da flag, mas também para suas combinações bit a bit. A resposta do eidolon está correta e sustenta que apenas combinações do enum de flag correto podem ser passadas como esse tipo.
12133 Scott

3
@ Scott: Vale a pena notar que o padrão C ++ define o intervalo válido de valores de uma instância enum dessa maneira. "para uma enumeração em que emin é o menor enumerador e emax é o maior, os valores da enumeração são os valores no intervalo bmin a bmax, definidos da seguinte forma: Seja K 1 para a representação do complemento de dois e 0 para os ' representação de complemento ou magnitude do sinal. bmax é o menor valor maior que ou igual a max(|emin| − K, |emax|)e igual a (1u<<M) - 1, onde Mé um número inteiro não negativo ".
Ben Voigt

Para quem (como eu) quer apenas algo prático que permita que os valores de enum sejam manipulados em bits e não pareça muito feio com modelos e conversão de tipos, essa é uma boa solução; apenas defina variáveis ​​para serem do tipo int.
Eric Sokolowsky

Observe também que no C ++, regular enumtecnicamente não é padronizado intcomo seu tipo subjacente (pré-C ++ 11 (IIRC) ou pós-C ++ 11 quando nenhum tipo subjacente é especificado), embora o enum class faça . Em vez disso, o tipo subjacente assume como padrão algo grande o suficiente para representar todos os enumeradores, com a única regra rígida real de que é maior do que intse explicitamente precisasse . Basicamente, o tipo subjacente é especificado como (parafraseado) "o que funcionar, mas provavelmente a int menos que os enumeradores sejam grandes demais para int".
Justin Time - Restabelece Monica

26

Acho a resposta atualmente aceita por eidolon muito perigosa. O otimizador do compilador pode fazer suposições sobre possíveis valores na enumeração e você pode receber lixo de volta com valores inválidos. E geralmente ninguém deseja definir todas as permutações possíveis em enumerações de sinalizadores.

Como Brian R. Bondy afirma abaixo, se você estiver usando C ++ 11 (o que todo mundo deveria, é bom), agora você pode fazer isso mais facilmente com 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. 

Isso garante um tamanho estável e um intervalo de valores, especificando um tipo para a enumeração, inibe a downcasting automática de enumerações para ints etc., usando enum classe usos constexprpara garantir que o código para os operadores seja embutido e, portanto, tão rápido quanto os números regulares.

Para pessoas presas com dialetos C ++ anteriores a 11

Se eu estivesse preso a um compilador que não suporta C ++ 11, agruparia um tipo int em uma classe que permita apenas o uso de operadores bit a bit e os tipos dessa enumeração para definir seus 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;
};

Você pode definir isso como uma enum regular + typedef:

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

typedef SafeEnum<enum TFlags_>  TFlags;

E o uso também é semelhante:

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;

E você também pode substituir o tipo subjacente para enumerações estáveis ​​em binários (como C ++ 11 enum foo : type) usando o segundo parâmetro de modelo, ie typedef SafeEnum<enum TFlags_,uint8_t> TFlags;.

Marquei a operator boolsubstituição com a explicitpalavra-chave do C ++ 11 para evitar que isso resultasse em conversões int, pois elas poderiam fazer com que conjuntos de sinalizadores acabassem colapsando em 0 ou 1 ao escrevê-los. Se você não pode usar o C ++ 11, deixe essa sobrecarga de fora e reescreva a primeira condicional no exemplo de uso como (myFlags & EFlagTwo) == EFlagTwo.


Como observação, eu recomendaria que o operador de exemplo definido no início use em std::underlying_typevez de codificar um tipo específico ou que o tipo subjacente seja fornecido e usado como um alias de tipo em vez de diretamente. Dessa forma, as alterações no tipo subjacente serão propagadas automaticamente, em vez de serem feitas manualmente.
Justin Time - Restabelece Monica

17

A maneira mais fácil de fazer isso, como mostrado aqui , usando o conjunto de bits da classe de biblioteca padrão .

Para emular o recurso C # de maneira segura, digite um wrapper de modelo em torno do bitset, substituindo os argumentos int por uma enum fornecida como parâmetro de tipo para o modelo. 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;

4
Veja isso para obter um código mais completo: codereview.stackexchange.com/questions/96146/…
Shital Shah

11

Na minha opinião, nenhuma das respostas até agora é ideal. Para ser ideal, eu esperaria a solução:

  1. Apoiar o ==, !=, =, &, &=, |, |=e ~operadores no sentido convencional (isto é, a & b)
  2. Seja seguro quanto ao tipo, ou seja, não permita que valores não enumerados, como literais ou tipos inteiros, sejam atribuídos (exceto combinações bit a bit de valores enumerados) ou permita que uma variável enum seja atribuída a um tipo inteiro
  3. Permitir expressões como if (a & b)...
  4. Não requer macros ruins, recursos específicos de implementação ou outros hacks

Até agora, a maioria das soluções recai nos pontos 2 ou 3. O WebDancer's é o fechamento na minha opinião, mas falha no ponto 3 e precisa ser repetido para cada enumeração.

Minha solução proposta é uma versão generalizada do WebDancer que também aborda o ponto 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;
}

Isso cria sobrecargas dos operadores necessários, mas usa o SFINAE para limitá-los a tipos enumerados. Observe que, por questões de brevidade, ainda não defini todos os operadores, mas o único que é diferente é o &. Atualmente, os operadores são globais (ou seja, aplicam-se a todos os tipos enumerados), mas isso pode ser reduzido colocando sobrecargas em um espaço para nome (o que eu faço) ou adicionando condições SFINAE adicionais (talvez usando tipos subjacentes específicos ou aliases de tipo especialmente criados ) O underlying_type_té um recurso do C ++ 14, mas parece ser bem suportado e é fácil de emular para o C ++ 11 com um simplestemplate<typename T> using underlying_type_t = underlying_type<T>::type;


Embora sua solução proposta funcione bem, ela também apresenta esse padrão para enumerações que não devem ser tratadas como sinalizadores. Essa é provavelmente a razão do uso de macros (más) como DEFINE_ENUM_FLAG_OPERATORS da Microsoft.
WebDancer 15/03/19

@WebDancer, é claro que você está correto, mas eu já disse isso na minha resposta. Também sugeri duas maneiras de resolver o problema - colocá-lo em um espaço para nome ou usar uma condição SFINAE mais restritiva.
Trevor

O que quero dizer é que, a menos que você crie um espaço para nome muito restrito (por exemplo, espaço para nome AllMyFlagEnums) ou tenha uma condição SFINAE que, de alguma forma, selecione apenas algumas enumerações exatas, o código está quebrado em minha mente. Em vez de arriscar isso, copio e colo um "modelo de texto" onde apenas substituo o nome da enumeração e, às vezes, as macros "más". Eu gostaria que houvesse uma maneira melhor.
WebDancer 19/03/19

Em primeiro lugar, isso só causará um problema se em algum lugar do seu código você precisar fazer uma das coisas que ele pretende parar, por exemplo, atribuir um literal, número inteiro ou elemento de outra enumeração. Caso contrário, o enum modificado se comporta como um enum regular, por exemplo, os elementos não precisam necessariamente ser poderes de dois e as operações de atribuição, comparação e bit a bit funcionam normalmente. Se você realmente precisar atribuir literais ou enumerações mistas, ainda poderá transmitir explicitamente, com a vantagem adicional de que sua intenção será mais clara. Portanto, é provável que não haja necessidade de reduzir o escopo.
21418 Trevor

Em segundo lugar, se você precisar reduzir o escopo, talvez o espaço para nome não precise ser restrito - embora isso dependa do que você está fazendo. Se você estiver trabalhando em uma biblioteca, talvez já tenha seu código que depende das enumerações de um espaço para nome, então o código da enumeração apenas entra no mesmo espaço de nome. Se você precisar do comportamento de enumeração de uma classe (talvez deseje usar as enumerações como argumentos de método ou variáveis ​​de membro da classe), coloque o código de enumeração na classe para o mesmo efeito. Resumindo, você não precisa agrupar um espaço de nome em torno das enumerações - embora possa.
Trevor

8

O padrão C ++ fala explicitamente sobre isso, consulte a seção "17.5.2.1.3 Tipos de máscaras":

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

Dado este "modelo", você obtém:

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;
}

E semelhante para os outros operadores. Observe também o "constexpr"; é necessário se você deseja que o compilador possa executar o tempo de compilação dos operadores.

Se você estiver usando C ++ / CLI e quiser atribuir a enum membros de classes ref, precisará usar referências de rastreamento:

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

NOTA: Este exemplo não está completo, consulte a seção "17.5.2.1.3 Tipos de máscaras" para obter um conjunto completo de operadores.


6

Eu me peguei fazendo a mesma pergunta e criei uma solução genérica baseada em C ++ 11, semelhante à 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);
    }   
};

A interface pode ser melhorada a gosto. Então pode ser usado assim:

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

2
Veja isso para obter um código melhor e mais completo: codereview.stackexchange.com/questions/96146/…
Shital Shah

5
Exceto pelo uso de numeric_limits, o código é quase o mesmo. Eu acho que é uma maneira comum de ter uma classe enum com segurança de tipo. Eu argumentaria que usar numeric_limits é melhor do que colocar um SENTINEL no final de cada enumeração.
Omair 30/05

1
Isso é um enorme conjunto de bits!
Lightness Races in Orbit -

(potencialmente ...)
Lightness Races in Orbit

5

Se o seu compilador ainda não suporta enumerações fortemente tipadas, consulte o seguinte artigo da fonte c ++:

Do resumo:

Este artigo apresenta uma solução para o problema de restringir as operações de bits para
permitir apenas operações seguras e legítimas e transformar todas as manipulações de bits inválidas em erros de compilação. O melhor de tudo é que a sintaxe das operações de bits permanece inalterada e o código que trabalha com bits não precisa ser modificado, exceto possivelmente para corrigir erros que ainda não haviam sido detectados.


5

Eu uso a seguinte 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) ); }

É semelhante aos mencionados acima, mas possui várias melhorias:

  • É do tipo seguro (não supõe que o tipo subjacente seja um int )
  • Não é necessário especificar manualmente o tipo subjacente (ao contrário da resposta de @LunarEclipse)

Ele precisa incluir type_traits:

#include <type_traits>

4

Gostaria de elaborar a resposta Uliwitness , corrigindo seu código para C ++ 98 e usando o idioma Safe Bool , por falta do std::underlying_type<>modelo e doexplicit palavra chave nas versões C ++ abaixo do C ++ 11.

Também modifiquei para que os valores de enum possam ser seqüenciais sem nenhuma atribuição explícita, para que você possa ter

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

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

Você pode obter o valor dos sinalizadores brutos com

seahawk.flags.value();

Aqui está o 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

Aqui está uma opção para máscaras de bits se você realmente não usar os valores de enum individuais (por exemplo, não precisa desativá-los) ... e se você não estiver preocupado em manter a compatibilidade binária, por exemplo: não importa onde moram seus bits ... o que você provavelmente está. Além disso, é melhor você não se preocupar muito com o escopo e o controle de acesso. Hmmm, enums têm boas propriedades para campos de bits ... pergunto se alguém já tentou isso :)

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 a vida é ótima, temos nossos valores discretos e temos uma boa intenção de & e | ao conteúdo de nossos corações, que ainda tem contexto do que seus bits significam. Tudo é consistente e previsível ... para mim ... desde que eu continue usando o compilador VC ++ da Microsoft com a Atualização 3 no Win10 x64 e não toque nos sinalizadores do meu compilador :)

Mesmo que tudo esteja ótimo ... temos algum contexto quanto ao significado das bandeiras agora, pois elas estão em uma união com o campo de bits no terrível mundo real, onde seu programa pode ser responsável por mais do que uma tarefa discreta que você poderia ainda acidentalmente (com muita facilidade) esmague dois campos de bandeiras de diferentes uniões (digamos, AnimalProperties e ObjectProperties, já que ambos são ints), misturando todos os seus bits, o que é um bug horrível para rastrear ... e como eu sei muitas pessoas neste post não trabalham com máscaras de bits com muita frequência, pois construí-las é fácil e mantê-las difíceis.

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 :(
}

Então, você torna sua declaração de união privada para impedir o acesso direto a "Flags", e precisa adicionar getters / setters e sobrecargas de operador, criar uma macro para tudo isso e você está basicamente no ponto em que começou quando tentou faça isso com um Enum.

Infelizmente, se você deseja que seu código seja portátil, acho que não há como A) garantir o layout de bits ou B) determinar o layout de bits no tempo de compilação (para que você possa rastreá-lo e, pelo menos, corrigir as alterações) versões / plataformas etc) Deslocamento em uma estrutura com campos de bits

Em tempo de execução, você pode executar truques com a definição dos campos e o XOR com as bandeiras para ver quais bits foram alterados, para mim parece bastante ruim, embora os versos tenham uma solução 100% consistente, independente da plataforma e completamente determinística, ou seja: um ENUM.

TL; DR: Não dê ouvidos aos inimigos. C ++ não é inglês. Só porque a definição literal de uma palavra-chave abreviada herdada de C pode não se adequar ao seu uso não significa que você não deve usá-la quando C e definição em C ++ da palavra-chave incluir absolutamente seu caso de uso. Você também pode usar estruturas para modelar outras coisas que não sejam estruturas e classes para outras que não sejam a escola e a casta social. Você pode usar float para valores aterrados. Você pode usar char para variáveis ​​que não são queimadas nem uma pessoa em um romance, peça ou filme. Qualquer programador que for ao dicionário para determinar o significado de uma palavra-chave antes da especificação do idioma é um ... bem, eu vou segurar minha língua lá.

Se você deseja que seu código seja modelado após a linguagem falada, é melhor escrever no Objective-C, que aliás também usa enumerações pesadamente para campos de bits.


3

Apenas açúcar sintático. Nenhum metadado adicional.

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;
}

Os operadores de sinalização no tipo integral simplesmente funcionam.


IMHO esta é a melhor resposta. Sintaxe de cliente fácil, limpa e simples. Eu usaria apenas "const int" em vez de "constexpr uint8_t", mas o conceito é o mesmo.
yoyo

(desculpe, "constexpr int")
yoyo

3

Atualmente, não há suporte de idioma para sinalizadores de enum, as classes Meta podem adicionar inerentemente esse recurso se ele fizer parte do padrão c ++.

Minha solução seria criar funções de modelo instanciadas somente enum adicionando suporte para operações bit a bit seguras para tipo para a classe enum usando seu tipo subjacente:

Arquivo: 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 conveniência e por reduzir erros, convém agrupar suas operações de sinalizadores de bit para enumerações e números inteiros:

Arquivo: 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_

Possível 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

O @Xaqq forneceu uma maneira muito agradável de usar tipos de sinalizadores de enum aqui por umflag_set classe.

Publiquei o código no GitHub , o uso é o seguinte:

#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

Você está confundindo objetos e coleções de objetos. Especificamente, você está confundindo sinalizadores binários com conjuntos de sinalizadores binários. Uma solução adequada seria assim:

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

Aqui está minha solução sem a necessidade de sobrecarga ou vazamento:

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);
}

Eu acho que está tudo bem, porque identificamos enums e ints (não fortemente tipados) de qualquer maneira.

Apenas como uma nota lateral (mais longa), se você

  • deseja usar enums fortemente tipados e
  • não precisa mexer pesado com suas bandeiras
  • desempenho não é um problema

Eu pensaria nisso:

#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 inicializadores C ++ 11 e enum class.


A propósito, prefiro não recomendar enums para bandeiras. Razão simples: combinações de sinalizadores não são elementos da enumeração novamente. Então isso parece bastante inadequado. Como alternativa, eu usaria um using Flags = unsigned longdentro de um espaço para nome ou estrutura contendo os próprios valores de sinalizador, como /*static*/ const Flags XY = 0x01assim por diante.
yau

1

Como acima (Kai) ou faça o seguinte. Enumerações realmente são "Enumerações", o que você quer fazer é ter um conjunto, portanto, você realmente deve 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

Talvez como NS_OPTIONS do 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.

Você pode explicar por que sua resposta é a mais adequada? Existem várias outras respostas que responderam a essa pergunta; portanto, inclua algumas informações para diferenciar as suas.
trevorp
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.