Um enum X : int
(C #) ou enum class X : int
(C ++ 11) é um tipo que possui um campo interno oculto int
que pode conter qualquer valor. Além disso, várias constantes predefinidas de X
são definidas na enumeração. É possível converter a enumeração em seu valor inteiro e vice-versa. Isso tudo é verdade em C # e C ++ 11.
Em C #, as enums não são usadas apenas para armazenar valores individuais, mas também para combinar combinações de bit a bit de sinalizadores, conforme recomendação da Microsoft . Tais enumerações são (geralmente, mas não necessariamente) decoradas com o [Flags]
atributo Para facilitar a vida dos desenvolvedores, os operadores bit a bit (OR, AND, etc ...) estão sobrecarregados para que você possa fazer algo assim (C #):
void M(NumericType flags);
M(NumericType.Sign | NumericType.ZeroPadding);
Eu sou um desenvolvedor experiente de C #, mas tenho programado C ++ apenas há alguns dias e não sou conhecido pelas convenções de C ++. Pretendo usar uma enumeração C ++ 11 da mesma maneira que costumava fazer em C #. No C ++ 11, os operadores bit a bit em enumerações com escopo não são sobrecarregados, então eu queria sobrecarregá-los .
Isso solicitou um debate, e as opiniões parecem variar entre três opções:
Uma variável do tipo enum é usada para conter o campo de bits, semelhante ao C #:
void M(NumericType flags); // With operator overloading: M(NumericType::Sign | NumericType::ZeroPadding); // Without operator overloading: M(static_cast<NumericType>(static_cast<int>(NumericType::Sign) | static_cast<int>(NumericType::ZeroPadding)));
Mas isso contraria a filosofia de enumeração fortemente tipada das enumerações com escopo definido em C ++ 11.
Use um número inteiro simples se desejar armazenar uma combinação de enumerações bit a bit:
void M(int flags); M(static_cast<int>(NumericType::Sign) | static_cast<int>(NumericType::ZeroPadding));
Mas isso reduziria tudo a um
int
, deixando você sem idéia de qual tipo você deveria colocar no método.Escreva uma classe separada que sobrecarregará os operadores e mantenha os sinalizadores bit a bit em um campo inteiro oculto:
class NumericTypeFlags { unsigned flags_; public: NumericTypeFlags () : flags_(0) {} NumericTypeFlags (NumericType t) : flags_(static_cast<unsigned>(t)) {} //...define BITWISE test/set operations }; void M(NumericTypeFlags flags); M(NumericType::Sign | NumericType::ZeroPadding);
( código completo do usuário315052 )
Mas você não possui o IntelliSense ou qualquer outro suporte para sugerir os valores possíveis.
Sei que essa é uma pergunta subjetiva , mas: que abordagem devo usar? Qual abordagem, se houver, é a mais amplamente reconhecida em C ++? Qual abordagem você usa ao lidar com campos de bits e por quê ?
É claro que, como todas as três abordagens funcionam, estou procurando razões factuais e técnicas, convenções geralmente aceitas e não apenas preferências pessoais.
Por exemplo, devido ao meu background em C #, eu costumo seguir a abordagem 1 em C ++. Isso tem o benefício adicional de que meu ambiente de desenvolvimento pode me sugerir os valores possíveis e, com operadores sobrecarregados de enum, isso é fácil de escrever e entender, e bastante limpo. E a assinatura do método mostra claramente que tipo de valor espera. Mas a maioria das pessoas aqui discorda de mim, provavelmente por um bom motivo.
fonte
enum E { A = 1, B = 2, C = 4, };
, o intervalo é0..7
(3 bits). Portanto, o padrão C ++ garante explicitamente que o número 1 sempre será uma opção viável. [Especificamente, oenum class
padrão é aquele aenum class : int
menos que seja especificado de outra forma e, portanto, sempre tem um tipo subjacente fixo.])Respostas:
A maneira mais simples é fornecer que o operador se sobrecarregue. Estou pensando em criar uma macro para expandir as sobrecargas básicas por tipo.
(Observe que
type_traits
é um cabeçalho C ++ 11 estd::underlying_type_t
é um recurso C ++ 14.)fonte
static_cast<T>
a entrada, mas a conversão no estilo C para o resultado aqui?SBJFrameDrag
for definido em uma classe e o|
operador for usado posteriormente nas definições da mesma classe, como você definiria o operador para que ele pudesse ser usado dentro da classe?Historicamente, eu sempre teria usado a enumeração antiga (de tipo fraco) para nomear as constantes de bits e apenas usado a classe de armazenamento explicitamente para armazenar o sinalizador resultante. Aqui, o ônus dependeria de mim para garantir que minhas enumerações se encaixassem no tipo de armazenamento e para acompanhar a associação entre o campo e as constantes relacionadas.
Gosto da ideia de enumerações fortemente tipadas, mas não me sinto muito à vontade com a ideia de que variáveis do tipo enumerado podem conter valores que não estão entre as constantes dessa enumeração.
Por exemplo, assumindo o bit a bit ou foi sobrecarregado:
Para sua terceira opção, você precisa de um padrão para extrair o tipo de armazenamento da enumeração. Supondo que queremos forçar um tipo subjacente não assinado (também podemos manipular assinados, com um pouco mais de código):
Isso ainda não oferece o IntelliSense ou o preenchimento automático, mas a detecção do tipo de armazenamento é menos feia do que eu esperava inicialmente.
Agora, encontrei uma alternativa: você pode especificar o tipo de armazenamento para uma enumeração de tipo fraco. Ele ainda tem a mesma sintaxe que em C #
Como é de tipo fraco e converte implicitamente para / de int (ou qualquer outro tipo de armazenamento que você escolher), é menos estranho ter valores que não correspondem às constantes enumeradas.
A desvantagem é que isso é descrito como "transitório" ...
NB essa variante adiciona suas constantes enumeradas ao escopo aninhado e ao anexo, mas você pode contornar isso com um espaço para nome:
fonte
Você pode definir sinalizadores de enumeração com segurança de tipo no C ++ 11 usando
std::enable_if
. Esta é uma implementação rudimentar que pode estar faltando algumas coisas:Observe que,number_of_bits
infelizmente, não pode ser preenchido pelo compilador, pois o C ++ não tem como fazer uma introspecção dos possíveis valores de uma enumeração.Edit: Na verdade, eu estou corrigido, é possível obter o preenchimento do compilador
number_of_bits
para você.Observe que isso pode manipular (de maneira ineficiente) um intervalo de valores de enum não contínuo. Digamos apenas que não é uma boa ideia usar o item acima com uma enumeração como esta ou com a loucura:
Mas todas as coisas consideradas são uma solução bastante útil no final. Não precisa de nenhuma manipulação de bits do lado do usuário, é seguro para o tipo e dentro de seus limites, o mais eficiente possível (estou me baseando fortemente na
std::bitset
qualidade da implementação aqui;)
).fonte
Eu
ódiodetesto macros no meu C ++ 14 tanto quanto o próximo, mas passei a usá-lo em todo o lugar, e de maneira bastante liberal também:Fazendo uso tão simples quanto
E, como se costuma dizer, a prova está no pudim:
Sinta-se livre para definir qualquer um dos operadores individuais como achar melhor, mas, na minha opinião altamente tendenciosa, o C / C ++ é para interface com conceitos e fluxos de baixo nível, e você pode retirar esses operadores bit a bit das minhas mãos frias e mortas e lutarei com você com todas as macros profanas e feitiços que eu posso invocar para mantê-las.
fonte
std::enable_if
comstd::is_enum
para restringir as sobrecargas gratuitas do operador a trabalhar apenas com tipos enumerados. Também adicionei operadores de comparação (usandostd::underlying_type
) e o operador não lógico para preencher ainda mais a lacuna sem perder a digitação forte. A única coisa que não pode corresponder é a conversão implícita para bool, masflags != 0
e!flags
são suficientes para mim.Normalmente, você define um conjunto de valores inteiros que correspondem aos números binários de um bit e os adiciona. É assim que os programadores C geralmente fazem isso.
Então você teria (usando o operador bitshift para definir os valores, por exemplo, 1 << 2 é o mesmo que 100 binário)
etc
No C ++, você tem mais opções, defina um novo tipo em vez de int (use typedef ) e defina valores semelhantes, como acima; ou defina um campo de bits ou um vetor de bools . Os dois últimos são muito eficientes em termos de espaço e fazem muito mais sentido para lidar com sinalizadores. Um campo de bits tem a vantagem de fornecer verificação de tipo (e, portanto, intellisense).
Eu diria (obviamente subjetivo) que um programador C ++ deve usar um campo de bits para o seu problema, mas eu costumo ver a abordagem #define usada pelos programas C muito nos programas C ++.
Suponho que o campo de bits seja o mais próximo do enum do C #, por que o C # tentou sobrecarregar um enum para ser do tipo de campo de bits é estranho - um enum deve realmente ser do tipo "seleção única".
fonte
0b0100
), para que o1 << n
formato fique meio obsoleto.Um pequeno exemplo de enum-flags abaixo, se parece muito com o C #.
Sobre a abordagem, na minha opinião: menos código, menos bugs, melhor código.
ENUM_FLAGS (T) é uma macro, definida em enum_flags.h (menos de 100 linhas, livre para usar sem restrições).
fonte
::type
lá. Corrigido: paste.ubuntu.com/23884820Existe ainda outra maneira de esfolar o gato:
Em vez de sobrecarregar os operadores de bit, pelo menos alguns podem preferir adicionar um liner 4 para ajudá-lo a contornar essa restrição desagradável de enumerações com escopo definido:
É verdade que você precisa digitar a
ut_cast()
coisa toda vez, mas, no lado superior, isso gera um código mais legível, no mesmo sentido que o usostatic_cast<>()
, comparado à conversão implícita de tipos ouoperator uint16_t()
coisas do tipo.E vamos ser honestos aqui, usar type
Foo
como no código acima tem seus perigos:Em outro lugar, alguém pode alternar entre variáveis
foo
e não esperar que ele tenha mais de um valor ...Assim, colocar o código de lado
ut_cast()
ajuda a alertar os leitores de que algo suspeito está acontecendo.fonte