Devo usar #define, enum ou const?

125

Em um projeto C ++ em que estou trabalhando, tenho um tipo de sinalizador que pode ter quatro valores. Essas quatro bandeiras podem ser combinadas. Os sinalizadores descrevem os registros no banco de dados e podem ser:

  • novo recorde
  • registro excluído
  • registro modificado
  • registro existente

Agora, para cada registro, desejo manter esse atributo, para que eu possa usar uma enumeração:

enum { xNew, xDeleted, xModified, xExisting }

No entanto, em outros lugares do código, preciso selecionar quais registros serão visíveis para o usuário, portanto, gostaria de passar isso como um único parâmetro, como:

showRecords(xNew | xDeleted);

Então, parece que tenho três possíveis apelações:

#define X_NEW      0x01
#define X_DELETED  0x02
#define X_MODIFIED 0x04
#define X_EXISTING 0x08

ou

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

ou

namespace RecordType {
    static const uint8 xNew = 1;
    static const uint8 xDeleted = 2;
    static const uint8 xModified = 4;
    static const uint8 xExisting = 8;
}

Os requisitos de espaço são importantes (byte vs int), mas não são cruciais. Com define, perco a segurança do tipo, e com enumperco algum espaço (inteiros) e provavelmente tenho que converter quando quero fazer uma operação bit a bit. Com consteu acho que também perco a segurança do tipo, pois um aleatório uint8pode entrar por engano.

Existe alguma outra maneira mais limpa?

Caso contrário, o que você usaria e por quê?

PS O restante do código é um C ++ moderno bastante limpo, sem #defines, e eu usei namespaces e templates em poucos espaços, portanto esses também não estão fora de questão.

Milan Babuškov
fonte
"com enum eu perco algum espaço (inteiros)". Não necessariamente. Veja stackoverflow.com/questions/366017/… e stackoverflow.com/questions/1113855/… (e gcc's -fshort-enum . (Estou assumindo que essas respostas C ainda sejam verdadeiras em C ++.)
idbrii
@pydave Caso não tenha certeza sobre a compatibilidade de C e C ++, acho este link muito útil; veja, por exemplo, enum david.tribble.com/text/cdiffs.htm#C99-enum-type
aka.nice
3
Este é um tópico antigo com votos altos. Existe um motivo para não mencionar as classes enumeradas do C ++ 11 para esta situação de problema.
Brandin
Como uma nota, enum RecordType : uint8_tcombina o tipo de segurança enumcom o tamanho pequeno de uint8_t, embora você ainda precise fornecer aos operadores um bit a bit.
Justin Time - Restabelece Monica

Respostas:

88

Combine as estratégias para reduzir as desvantagens de uma única abordagem. Como trabalho em sistemas embarcados, a solução a seguir é baseada no fato de que operadores inteiros e bit a bit são rápidos, com pouca memória e pouco uso de flash.

Coloque a enumeração em um espaço para nome para impedir que as constantes poluam o espaço para nome global.

namespace RecordType {

Uma enum declara e define um tempo de compilação verificado digitado. Sempre use a verificação do tipo de tempo de compilação para garantir que argumentos e variáveis ​​recebam o tipo correto. Não há necessidade do typedef em C ++.

enum TRecordType { xNew = 1, xDeleted = 2, xModified = 4, xExisting = 8,

Crie outro membro para um estado inválido. Isso pode ser útil como código de erro; por exemplo, quando você deseja retornar o estado, mas a operação de E / S falha. Também é útil para depuração; use-o nas listas de inicialização e destruidores para saber se o valor da variável deve ser usado.

xInvalid = 16 };

Considere que você tem dois propósitos para esse tipo. Para rastrear o estado atual de um registro e criar uma máscara para selecionar registros em determinados estados. Crie uma função embutida para testar se o valor do tipo é válido para sua finalidade; como um marcador de estado vs uma máscara de estado. Isso detectará bugs, pois o valor typedefé apenas um inte um valor que 0xDEADBEEFpode estar na sua variável por meio de variáveis ​​não inicializadas ou mal direcionadas.

inline bool IsValidState( TRecordType v) {
    switch(v) { case xNew: case xDeleted: case xModified: case xExisting: return true; }
    return false;
}

 inline bool IsValidMask( TRecordType v) {
    return v >= xNew  && v < xInvalid ;
}

Adicione uma usingdiretiva se desejar usar o tipo com frequência.

using RecordType ::TRecordType ;

As funções de verificação de valor são úteis nas afirmações para interceptar valores ruins assim que são usados. Quanto mais rápido você pegar um bug durante a execução, menos danos ele poderá causar.

Aqui estão alguns exemplos para juntar tudo.

void showRecords(TRecordType mask) {
    assert(RecordType::IsValidMask(mask));
    // do stuff;
}

void wombleRecord(TRecord rec, TRecordType state) {
    assert(RecordType::IsValidState(state));
    if (RecordType ::xNew) {
    // ...
} in runtime

TRecordType updateRecord(TRecord rec, TRecordType newstate) {
    assert(RecordType::IsValidState(newstate));
    //...
    if (! access_was_successful) return RecordType ::xInvalid;
    return newstate;
}

A única maneira de garantir a segurança correta dos valores é usar uma classe dedicada com sobrecargas do operador e isso é deixado como exercício para outro leitor.

mat_geek
fonte
1
Principalmente uma boa resposta - mas a pergunta estipula que os sinalizadores podem ser combinados e a função IsValidState () não permite que eles sejam combinados.
Jonathan Leffler
3
@ Jonathan Leffler: de onde eu estou, acho que o 'IsValidState' não deve fazer isso, 'IsValidMask' é.
João Portela
1
É desejado que IsValidMasknão permita a seleção de nenhum (ie 0)?
Joachim Sauer
2
-1 A idéia da verificação do tipo de tempo de execução é uma abominação.
Saúde e hth. #
54

Esqueça as definições

Eles poluirão seu código.

campos de bits?

struct RecordFlag {
    unsigned isnew:1, isdeleted:1, ismodified:1, isexisting:1;
};

Nunca use isso . Você está mais preocupado com a velocidade do que com a economia de 4 polegadas. Usar campos de bits é realmente mais lento que o acesso a qualquer outro tipo.

No entanto, membros de bit em estruturas têm desvantagens práticas. Primeiro, a ordem dos bits na memória varia de compilador para compilador. Além disso, muitos compiladores populares geram código ineficiente para leitura e gravação de membros de bits , e há problemas de segurança de encadeamento potencialmente graves relacionados a campos de bits (especialmente em sistemas com multiprocessadores) devido ao fato de a maioria das máquinas não poder manipular conjuntos arbitrários de bits na memória, mas deve carregar e armazenar palavras inteiras. por exemplo, o seguinte não seria seguro para threads, apesar do uso de um mutex

Fonte: http://en.wikipedia.org/wiki/Bit_field :

E se você precisar de mais motivos para não usar campos de bits, talvez Raymond Chen o convença em The Old New Thing Post: A análise de custo-benefício de campos de bits para uma coleção de booleanos em http://blogs.msdn.com/oldnewthing/ archive / 2008/11/26 / 9143050.aspx

const int?

namespace RecordType {
    static const uint8 xNew = 1;
    static const uint8 xDeleted = 2;
    static const uint8 xModified = 4;
    static const uint8 xExisting = 8;
}

Colocá-los em um espaço para nome é legal. Se eles forem declarados no seu CPP ou arquivo de cabeçalho, seus valores serão incorporados. Você poderá usar esses valores, mas isso aumentará um pouco o acoplamento.

Ah, sim: remova a palavra-chave estática . static está obsoleto no C ++ quando usado como você e, se uint8 for do tipo buildin, não será necessário declarar isso em um cabeçalho incluído por várias fontes do mesmo módulo. No final, o código deve ser:

namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

O problema dessa abordagem é que seu código conhece o valor de suas constantes, o que aumenta um pouco o acoplamento.

enum

O mesmo que const int, com uma digitação um pouco mais forte.

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

Eles ainda estão poluindo o espaço para nome global, no entanto. A propósito ... Remova o typedef . Você está trabalhando em C ++. Esses typedefs de enumerações e estruturas estão poluindo o código mais do que qualquer outra coisa.

O resultado é meio que:

enum RecordType { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } ;

void doSomething(RecordType p_eMyEnum)
{
   if(p_eMyEnum == xNew)
   {
       // etc.
   }
}

Como você vê, sua enumeração está poluindo o espaço para nome global. Se você colocar esse enum em um espaço para nome, terá algo como:

namespace RecordType {
   enum Value { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } ;
}

void doSomething(RecordType::Value p_eMyEnum)
{
   if(p_eMyEnum == RecordType::xNew)
   {
       // etc.
   }
}

extern const int?

Se você quiser diminuir o acoplamento (ou seja, poder ocultar os valores das constantes e modificá-los conforme desejado, sem precisar de uma recompilação completa), poderá declarar as entradas como externas no cabeçalho e constantes no arquivo CPP , como no exemplo a seguir:

// Header.hpp
namespace RecordType {
    extern const uint8 xNew ;
    extern const uint8 xDeleted ;
    extern const uint8 xModified ;
    extern const uint8 xExisting ;
}

E:

// Source.hpp
namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

Você não poderá usar o switch nessas constantes. Então, no final, escolha seu veneno ... :-p

paercebal
fonte
5
Por que você acha que os campos de bits são lentos? Você realmente criou o código do perfil usando-o e outro método? Mesmo que seja, a clareza pode ser mais importante que a velocidade, simplificando um pouco "nunca use isso".
wnoise 22/09/08
"estática const uint8 xNew;" é redundante apenas porque nas variáveis ​​com escopo de espaço de nome const C ++ padrão o vínculo interno. Remova "const" e ele tem ligação externa. Além disso, "enum {...} RecordType;" declara uma variável global denominada "RecordType" cujo tipo é uma enumeração anônima.
bk1e
onebyone: Primeiro, a principal razão foi que o ganho (alguns bytes, se houver) foi overhsadowed pela perda (mais lento para acessar, ler e escrever) ...
paercebal
3
onebyone: Segundo, todo o código que produzo no trabalho ou em casa é inerentemente seguro para threads. É fácil de fazer: sem globais, sem estática, sem compartilhamento entre threads, a menos que estejam protegidos por bloqueio. Usar esse idioma quebraria essa segurança básica de thread. E para quê? Talvez alguns bytes ? ... :-) ...
paercebal
Referência adicionada ao artigo de Raymond Chen sobre os custos ocultos dos campos de bits.
28468 paercebal
30

Você descartou std :: bitset? Conjuntos de bandeiras é o que serve. Faz

typedef std::bitset<4> RecordType;

então

static const RecordType xNew(1);
static const RecordType xDeleted(2);
static const RecordType xModified(4);
static const RecordType xExisting(8);

Como existem várias sobrecargas de operador para o conjunto de bits, agora você pode fazer

RecordType rt = whatever;      // unsigned long or RecordType expression
rt |= xNew;                    // set 
rt &= ~xDeleted;               // clear 
if ((rt & xModified) != 0) ... // test

Ou algo muito semelhante a isso - eu apreciaria qualquer correção, pois não testei isso. Você também pode consultar os bits por índice, mas geralmente é melhor definir apenas um conjunto de constantes, e as constantes RecordType são provavelmente mais úteis.

Supondo que você tenha descartado o bitset, voto a favor da enumeração .

Eu não acredito que lançar enums seja uma séria desvantagem - OK, então é um pouco barulhento e atribuir um valor fora do intervalo a um enum é um comportamento indefinido, portanto é teoricamente possível dar um tiro no pé em algum C ++ incomum implementações. Mas se você fizer isso apenas quando necessário (que é quando passa de int para enum iirc), é um código perfeitamente normal que as pessoas já viram antes.

Também duvido de qualquer custo de espaço do enum. As variáveis ​​e parâmetros do uint8 provavelmente não usarão menos pilha do que ints, portanto, apenas o armazenamento nas classes é importante. Existem alguns casos em que o empacotamento de vários bytes em uma estrutura vencerá (nesse caso, você poderá converter enums dentro e fora do armazenamento uint8), mas normalmente o preenchimento eliminará o benefício de qualquer maneira.

Portanto, o enum não tem desvantagens em comparação com os outros e, como vantagem, oferece um pouco de segurança de tipo (você não pode atribuir um valor inteiro aleatório sem converter explicitamente) e maneiras limpas de se referir a tudo.

De preferência, eu também colocaria o "= 2" na enumeração, a propósito. Não é necessário, mas um "princípio de menor espanto" sugere que todas as quatro definições devem ter a mesma aparência.

Steve Jessop
fonte
1
Na verdade, eu não considerei o conjunto de bits. No entanto, não tenho certeza se seria bom. Com o bitset, tenho que endereçar os bits como 1, 2, 3, 4, o que tornaria o código menos legível - o que significa que eu provavelmente usaria uma enumeração para 'nomear' os bits. Poderia ser um economizador de espaço embora. Obrigado.
Milan Babuškov 21/09/08
Milão, você não precisa "nomear" os bits usando uma enumeração, basta usar os bits predefinidos, como mostrado acima. Se você deseja ativar o bit um, em vez de my_bitset.flip (1), você faria my_bitset | = xNew;
moswald 22/09/08
isso é direcionado menos para você e mais para o STL, mas: eu realmente tenho que perguntar: por que você usaria bitsetpara isso? ele geralmente se traduz em um long(na minha implementação iirc; sim, que desperdício) ou tipo integral semelhante para cada elemento, então porque não usar apenas integrais não ofuscadas? (ou hoje em dia, constexprcom armazenamento zero)
underscore_d
[editar o tempo limite] ... mas nunca entendi realmente a lógica da bitsetclasse, além do que parece ser uma tendência recorrente nas discussões em torno de 'ugh, precisamos encobrir as raízes desagradáveis ​​de baixo nível da linguagem ''
underscore_d
" uint8variáveis ​​e parâmetros provavelmente não usarão menos pilha do que ints" está errado. Se você possui uma CPU com registradores de 8 bits, são intnecessários pelo menos 2 registradores e uint8_tapenas 1, portanto, será necessário mais espaço de pilha, pois é mais provável que você esteja sem registradores (o que também é mais lento e pode aumentar o tamanho do código ( dependendo do conjunto de instruções)). (Você tem um tipo, deve ser uint8_tnão uint8)
12431234123412341234123
5

Se possível, NÃO use macros. Eles não são muito admirados quando se trata de C ++ moderno.

INS
fonte
4
Verdade. O que eu odeio nas macros é que você não pode entrar nelas se elas estiverem erradas.
3030 Carl
Eu imagino que seja algo que possa ser corrigido no compilador.
Celticminstrel 5/07
4

As enums seriam mais apropriadas, pois fornecem "significado aos identificadores" e também segurança de tipo. Você pode dizer claramente que "xDeleted" é de "RecordType" e que representa "tipo de registro" (uau!), Mesmo depois de anos. Os consts exigiriam comentários para isso, também exigiriam subir e descer no código.

hayalci
fonte
4

Com define eu perco a segurança do tipo

Não necessariamente...

// signed defines
#define X_NEW      0x01u
#define X_NEW      (unsigned(0x01))  // if you find this more readable...

e com enum eu perco algum espaço (inteiros)

Não necessariamente - mas você precisa ser explícito nos pontos de armazenamento ...

struct X
{
    RecordType recordType : 4;  // use exactly 4 bits...
    RecordType recordType2 : 4;  // use another 4 bits, typically in the same byte
    // of course, the overall record size may still be padded...
};

e provavelmente terá que transmitir quando eu quiser fazer uma operação bit a bit.

Você pode criar operadores para aliviar isso:

RecordType operator|(RecordType lhs, RecordType rhs)
{
    return RecordType((unsigned)lhs | (unsigned)rhs);
}

Com const, acho que também perco a segurança de tipos, pois um uint8 aleatório pode entrar por engano.

O mesmo pode acontecer com qualquer um desses mecanismos: as verificações de alcance e valor são normalmente ortogonais para digitar segurança (embora os tipos definidos pelo usuário - ou seja, suas próprias classes - possam impor "invariantes" a seus dados). Com enumerações, o compilador é livre para escolher um tipo maior para hospedar os valores, e uma variável de enumer não inicializada, corrompida ou com apenas um conjunto incorreto ainda pode acabar interpretando seu padrão de bits como um número que você não esperaria - comparando desigual a qualquer um dos os identificadores de enumeração, qualquer combinação deles e 0.

Existe alguma outra maneira mais limpa? / Se não, o que você usaria e por quê?

Bem, no final, o OR de bit a bit, experimentado e confiável, no estilo C, funciona muito bem quando você tem campos de bits e operadores personalizados na imagem. Você pode melhorar ainda mais sua robustez com algumas funções e asserções de validação personalizadas, como na resposta de mat_geek; técnicas geralmente igualmente aplicáveis ​​ao manuseio de strings, int, valores duplos etc.

Você poderia argumentar que isso é "mais limpo":

enum RecordType { New, Deleted, Modified, Existing };

showRecords([](RecordType r) { return r == New || r == Deleted; });

Sou indiferente: os bits de dados são mais compactos, mas o código cresce significativamente ... depende de quantos objetos você tem, e os lamdbas - por mais bonitos que sejam - ainda são mais confusos e difíceis de acertar do que as ORs bit a bit.

BTW / - o argumento sobre o IMHO bastante fraco da segurança de threads - deve ser lembrado como uma consideração de segundo plano, em vez de se tornar uma força dominante na tomada de decisões; compartilhar um mutex entre os campos de bits é uma prática mais provável, mesmo que não tenha conhecimento de sua compactação (os mutexes são membros de dados relativamente volumosos - eu tenho que estar realmente preocupado com o desempenho para considerar a possibilidade de ter vários mutexes nos membros de um objeto, e eu observaria cuidadosamente o suficiente para notar que eram campos de bits). Qualquer tipo de tamanho de sub-palavra pode ter o mesmo problema (por exemplo, a uint8_t). De qualquer forma, você pode tentar operações atômicas no estilo de comparar e trocar se estiver desesperado por uma simultaneidade mais alta.

Tony Delroy
fonte
1
+1 Tudo bem. Mas o operator|deve converter para um tipo inteiro ( unsigned int) antes da instrução |. Caso operator|contrário, a chamada se chamará recursivamente e causará um estouro de pilha em tempo de execução. Eu sugiro: return RecordType( unsigned(lhs) | unsigned(rhs) );. Cheers
olibre
3

Mesmo se você precisar usar 4 bytes para armazenar uma enumeração (não conheço o C ++ - sei que é possível especificar o tipo subjacente em C #), ainda vale a pena - use enumerações.

Hoje em dia, os servidores com GBs de memória, coisas como 4 bytes vs. 1 byte de memória no nível do aplicativo em geral não importam. Obviamente, se em sua situação específica, o uso de memória é tão importante (e você não pode fazer com que o C ++ use um byte para fazer backup da enumeração), considere a rota 'estática const'.

No final do dia, você deve se perguntar: vale a pena o trabalho de manutenção usando 'const estática' para os 3 bytes de economia de memória para sua estrutura de dados?

Outra coisa a ter em mente - o IIRC, no x86, as estruturas de dados são alinhadas em 4 bytes, portanto, a menos que você tenha vários elementos de largura de bytes em sua estrutura de 'registro', isso pode não ser realmente importante. Teste e certifique-se de que sim antes de fazer uma troca na capacidade de manutenção por desempenho / espaço.

Jonathan Rupp
fonte
Você pode especificar o tipo subjacente em C ++, como na revisão de linguagem C ++ 11. Até então, acredito que era "pelo menos grande o suficiente para armazenar e ser usado como um campo de bits para todos os enumeradores especificados, mas provavelmente a intmenos que seja muito pequeno". [Se você não especificar o tipo subjacente no C ++ 11, ele usará o comportamento herdado. Por outro lado, o enum classtipo subjacente do C ++ 11 padroniza explicitamente intse não for especificado de outra forma.]
Justin Time - Restabelece Monica em
3

Se você deseja o tipo de segurança das classes, com a conveniência da sintaxe da enumeração e da verificação de bits, considere Rótulos Seguros em C ++ . Eu trabalhei com o autor, e ele é muito inteligente.

Cuidado, no entanto. No final, este pacote usa modelos e macros!

Don Wakefield
fonte
Parece exagero para o meu pequeno aplicativo. mas parece ser uma boa solução.
Milan Babuškov 22/09/08
2

Você realmente precisa passar os valores da flag como um todo conceitual ou terá muito código por flag? De qualquer forma, acho que ter isso como classe ou estrutura de campos de bits de 1 bit pode ser mais claro:

struct RecordFlag {
    unsigned isnew:1, isdeleted:1, ismodified:1, isexisting:1;
};

Em seguida, sua classe de registro pode ter uma variável de membro struct RecordFlag, funções podem receber argumentos do tipo struct RecordFlag, etc. O compilador deve agrupar os campos de bits, economizando espaço.

wnoise
fonte
Às vezes como um todo, às vezes como bandeira. E também preciso testar se um determinado sinalizador está definido (quando o passo como um todo).
Milan Babuškov 21/09/08
bem, quando separado, basta pedir um int. Quando juntos, passam a estrutura.
wnoise 21/09/08
Não vai ser melhor. O acesso aos campos de bits é mais lento que qualquer outra coisa.
paercebal 21/09/08
Realmente? Você acha que o compilador irá gerar um código significativamente diferente para testar os campos de bits do que a manipulação manual de bits? E que será significativamente mais lento? Por quê? A única coisa que você não pode fazer de maneira tão fácil de idioma é mascarar vários sinalizadores ao mesmo tempo.
wnoise 21/09/08
Executando um teste de leitura simples, recebo 5,50-5,58 segundos para mascaramento de bits versus 5,45-5,59 para acesso a campo de bits. Praticamente indistinguível.
wnoise 22/09/08
2

Eu provavelmente não usaria uma enumeração para esse tipo de coisa em que os valores podem ser combinados, mais tipicamente enumerações são estados mutuamente exclusivos.

Porém, seja qual for o método usado, para deixar mais claro que esses são valores que são bits que podem ser combinados, use esta sintaxe para os valores reais:

#define X_NEW      (1 << 0)
#define X_DELETED  (1 << 1)
#define X_MODIFIED (1 << 2)
#define X_EXISTING (1 << 3)

Usar um deslocamento para a esquerda ajuda a indicar que cada valor se destina a ser um único bit, é menos provável que mais tarde alguém faça algo errado, como adicionar um novo valor e atribuir a ele um valor 9.


fonte
1
Há precedentes suficientes para isso, principalmente em constantes para ioctl (). Eu prefiro usar constantes hexadecimais, embora: 0x01, 0x02, 0x04, 0x08, 0x10, ...
Jonathan Leffler
2

Com base no KISS , alta coesão e baixo acoplamento , faça estas perguntas -

  • Quem precisa saber? minha classe, minha biblioteca, outras classes, outras bibliotecas, terceiros
  • Que nível de abstração eu preciso fornecer? O consumidor entende as operações de bits.
  • Vou ter que fazer interface de VB / C # etc?

Existe um ótimo livro " Design de software C ++ em larga escala ", que promove tipos de base externamente, se você puder evitar outra dependência de interface / arquivo de cabeçalho que deve tentar.

titanae
fonte
1
a) 5-6 aulas. b) somente eu, é um projeto individual c) sem interface
Milan Babuškov 22/09/08
2

Se você estiver usando o Qt, procure QFlags . A classe QFlags fornece uma maneira segura de armazenar as combinações OR de valores de enumeração.

Thomas Koschel
fonte
Não, não Qt. Na verdade, é um projeto wxWidgets.
Milan Babuškov 22/09/08
0

Eu prefiro ir com

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

Simplesmente porque:

  1. É mais limpo e torna o código legível e de manutenção.
  2. Agrupa logicamente as constantes.
  3. O tempo do programador é mais importante, a menos que seu trabalho seja salvar esses 3 bytes.
Vivek
fonte
Bem, eu poderia facilmente ter um milhão de instâncias da classe Record, por isso pode ser importante. OTOH, isso é apenas uma diferença entre 1 MB e 4 MB, então talvez eu não deva me preocupar.
Milan Babuškov 21/09/08
@Vivek: Você considerou a limitação da largura inteira? Em particular antes do C ++ 11.
user2672165
0

Não que eu goste de projetar demais tudo, mas, às vezes, nesses casos, pode valer a pena criar uma classe (pequena) para encapsular essas informações. Se você criar uma classe RecordType, ela poderá ter funções como:

void setDeleted ();

void clearDeleted ();

bool isDeleted ();

etc ... (ou qualquer que seja a convenção)

Poderia validar combinações (no caso em que nem todas as combinações são legais, por exemplo, se 'novo' e 'excluído' não pudessem ser definidos ao mesmo tempo). Se você acabou de usar máscaras de bits etc., o código que define o estado precisa ser validado, uma classe também pode encapsular essa lógica.

A classe também pode dar a você a capacidade de anexar informações de log significativas a cada estado; você pode adicionar uma função para retornar uma representação em cadeia do estado atual, etc. (ou usar os operadores << »).

Por tudo isso, se você está preocupado com o armazenamento, você ainda pode ter a classe apenas com um membro de dados 'char', portanto, leve apenas uma pequena quantidade de armazenamento (supondo que não seja virtual). Obviamente, dependendo do hardware, etc., você pode ter problemas de alinhamento.

Você pode ter os valores de bits reais não visíveis para o resto do 'mundo' se eles estiverem em um espaço para nome anônimo dentro do arquivo cpp, e não no arquivo de cabeçalho.

Se você achar que o código que usa o enum / # define / bitmask etc tem muito código de 'suporte' para lidar com combinações inválidas, registros etc., então o encapsulamento em uma classe pode ser considerado. É claro que na maioria das vezes problemas simples são melhores com soluções simples ...

Benjamin
fonte
Infelizmente, a declaração precisa estar em um arquivo .h, pois é usada em todo o projeto (usada por 5-6 classes).
Milan Babuškov 22/09/08