Tratar enum
s 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.flags
como 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
.
[Flags]
atributo funciona muito bem, ou seja:[Flags] enum class FlagBits{ Ready = 1, ReadMode = 2, WriteMode = 4, EOF = 8, Disabled = 16};
Respostas:
A maneira "correta" é definir operadores de bits para a enumeração, como:
Etc. resto dos operadores de bits. Modifique conforme necessário se o intervalo de enum exceder o intervalo int.
fonte
AnimalFlags
é representado pela expressãoHasClaws | CanFly
? Não é para isso queenum
servem. Use números inteiros e constantes.(HasClaws | CanFly)
".HasClaws
(= 1) eCanFly
(= 2). Se, em vez disso, você apenas atribuir os valores de 1 a 4 e conseguir um 3, pode ser um únicoEatsFish
, ou novamente uma combinação deHasClaws
eCanFly
. 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.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.
Ele pode manter valores até um int, ou seja, na maioria das vezes, 32 sinalizadores, o que é claramente refletido no valor do turno.
fonte
Para pessoas preguiçosas como eu, aqui está a solução para copiar e colar:
fonte
using
, quando apropriado, assim comorel_ops
.Observe que se você estiver trabalhando no ambiente Windows, há uma
DEFINE_ENUM_FLAG_OPERATORS
macro definida em winnt.h que faz o trabalho para você. Portanto, neste caso, você pode fazer o seguinte:fonte
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 ...
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 ....
fonte
HasClaws | CanFly
é algum tipo inteiro, mas o tipo deHasClaws
éAnimalFlags
, não um tipo inteiro.max(|emin| − K, |emax|)
e igual a(1u<<M) - 1
, ondeM
é um número inteiro não negativo ".int
.enum
tecnicamente não é padronizadoint
como seu tipo subjacente (pré-C ++ 11 (IIRC) ou pós-C ++ 11 quando nenhum tipo subjacente é especificado), embora oenum 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 queint
se explicitamente precisasse . Basicamente, o tipo subjacente é especificado como (parafraseado) "o que funcionar, mas provavelmente aint
menos que os enumeradores sejam grandes demais paraint
".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
: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 class
e usosconstexpr
para 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:
Você pode definir isso como uma enum regular + typedef:
E o uso também é semelhante:
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, ietypedef SafeEnum<enum TFlags_,uint8_t> TFlags;
.Marquei a
operator bool
substituição com aexplicit
palavra-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
.fonte
std::underlying_type
vez 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.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:
fonte
Na minha opinião, nenhuma das respostas até agora é ideal. Para ser ideal, eu esperaria a solução:
==
,!=
,=
,&
,&=
,|
,|=
e~
operadores no sentido convencional (isto é,a & b
)if (a & b)...
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:
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 ) Ounderlying_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;
fonte
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:
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:
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.
fonte
Eu me peguei fazendo a mesma pergunta e criei uma solução genérica baseada em C ++ 11, semelhante à soru:
A interface pode ser melhorada a gosto. Então pode ser usado assim:
fonte
Se o seu compilador ainda não suporta enumerações fortemente tipadas, consulte o seguinte artigo da fonte c ++:
Do resumo:
fonte
Eu uso a seguinte macro:
É semelhante aos mencionados acima, mas possui várias melhorias:
int
)Ele precisa incluir type_traits:
fonte
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
Você pode obter o valor dos sinalizadores brutos com
Aqui está o código.
fonte
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 :)
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.
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.
fonte
Apenas açúcar sintático. Nenhum metadado adicional.
Os operadores de sinalização no tipo integral simplesmente funcionam.
fonte
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
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
Possível uso:
fonte
O @Xaqq forneceu uma maneira muito agradável de usar tipos de sinalizadores de enum aqui por um
flag_set
classe.Publiquei o código no GitHub , o uso é o seguinte:
fonte
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:
fonte
Aqui está minha solução sem a necessidade de sobrecarga ou vazamento:
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ê
Eu pensaria nisso:
usando listas de inicializadores C ++ 11 e
enum class
.fonte
using Flags = unsigned long
dentro de um espaço para nome ou estrutura contendo os próprios valores de sinalizador, como/*static*/ const Flags XY = 0x01
assim por diante.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
fonte
Talvez como NS_OPTIONS do Objective-C.
fonte