Dans un projet C ++ sur lequel je travaille, j'ai une sorte d' indicateur de valeur qui peut avoir quatre valeurs. Ces quatre drapeaux peuvent être combinés. Les indicateurs décrivent les enregistrements de la base de données et peuvent être:
- nouvel enregistrement
- enregistrement supprimé
- enregistrement modifié
- enregistrement existant
Maintenant, pour chaque enregistrement, je souhaite conserver cet attribut, afin que je puisse utiliser une énumération:
enum { xNew, xDeleted, xModified, xExisting }
Cependant, à d'autres endroits dans le code, je dois sélectionner les enregistrements qui doivent être visibles par l'utilisateur, donc j'aimerais pouvoir le transmettre en tant que paramètre unique, comme:
showRecords(xNew | xDeleted);
Donc, il semble que j'ai trois approches possibles:
#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;
}
Les besoins en espace sont importants (octet vs int) mais pas cruciaux. Avec les définitions, je perds la sécurité de type, et avec enum
je perds de l'espace (entiers) et je dois probablement effectuer un cast quand je veux faire une opération au niveau du bit. Avec const
je pense que je perds aussi la sécurité de type car un aléatoire uint8
pourrait entrer par erreur.
Existe-t-il un autre moyen plus propre?
Sinon, qu'utiliseriez-vous et pourquoi?
PS Le reste du code est plutôt propre C ++ moderne sans #define
s, et j'ai utilisé des espaces de noms et des modèles dans quelques espaces, donc ceux-ci ne sont pas non plus hors de question.
la source
enum RecordType : uint8_t
combine le type de sécurité deenum
avec la petite taille deuint8_t
, bien que vous deviez toujours fournir des opérateurs au niveau du bit.Réponses:
Combinez les stratégies pour réduire les inconvénients d'une seule approche. Je travaille dans des systèmes embarqués, donc la solution suivante est basée sur le fait que les opérateurs entiers et bit à bit sont rapides, à faible mémoire et à faible utilisation du flash.
Placez l'énumération dans un espace de noms pour empêcher les constantes de polluer l'espace de noms global.
Une énumération déclare et définit une heure de compilation vérifiée typée. Utilisez toujours la vérification du type au moment de la compilation pour vous assurer que les arguments et les variables reçoivent le type correct. Le typedef n'est pas nécessaire en C ++.
Créez un autre membre pour un état non valide. Cela peut être utile comme code d'erreur; par exemple, lorsque vous souhaitez renvoyer l'état mais que l'opération d'E / S échoue. Il est également utile pour le débogage; utilisez-le dans les listes d'initialisation et les destructeurs pour savoir si la valeur de la variable doit être utilisée.
Considérez que vous avez deux objectifs pour ce type. Pour suivre l'état actuel d'un enregistrement et pour créer un masque pour sélectionner des enregistrements dans certains états. Créez une fonction en ligne pour tester si la valeur du type est valide pour votre objectif; comme marqueur d'état par rapport à un masque d'état. Cela attrapera des bugs car le
typedef
est juste unint
et une valeur telle que0xDEADBEEF
peut être dans votre variable via des variables non initialisées ou mal pointées.Ajouter un
using
directive si vous souhaitez utiliser le type souvent.Les fonctions de vérification des valeurs sont utiles dans les assertions pour intercepter les mauvaises valeurs dès qu'elles sont utilisées. Plus vite vous attrapez un bug lors de l'exécution, moins il peut faire de dégâts.
Voici quelques exemples pour mettre tout cela ensemble.
La seule façon de garantir la sécurité de la valeur correcte est d'utiliser une classe dédiée avec des surcharges d'opérateur et qui est laissée comme exercice pour un autre lecteur.
la source
IsValidMask
ne permette pas de sélectionner aucun (ie0
)?Oubliez les définitions
Ils pollueront votre code.
bitfields?
N'utilisez jamais ça . Vous êtes plus soucieux de la vitesse que d'économiser 4 ints. L'utilisation de champs de bits est en fait plus lente que l'accès à tout autre type.
Source: http://en.wikipedia.org/wiki/Bit_field :
Et si vous avez besoin de plus de raisons pour ne pas utiliser de champs de bits, peut-être que Raymond Chen vous convaincra dans son article The Old New Thing : L'analyse coût-bénéfice des champs de bits pour une collection de booléens à http://blogs.msdn.com/oldnewthing/ archive / 2008/11/26 / 9143050.aspx
const int?
Les mettre dans un espace de noms est cool. S'ils sont déclarés dans votre fichier CPP ou d'en-tête, leurs valeurs seront insérées. Vous pourrez utiliser le commutateur sur ces valeurs, mais cela augmentera légèrement le couplage.
Ah, oui: supprimez le mot-clé static . static est obsolète en C ++ lorsqu'il est utilisé comme vous le faites, et si uint8 est un type intégré, vous n'en aurez pas besoin pour le déclarer dans un en-tête inclus par plusieurs sources du même module. En fin de compte, le code devrait être:
Le problème de cette approche est que votre code connaît la valeur de vos constantes, ce qui augmente légèrement le couplage.
énumération
Identique à const int, avec un typage un peu plus fort.
Cependant, ils polluent toujours l'espace de noms mondial. Au fait ... Supprimez le typedef . Vous travaillez en C ++. Ces typedefs d'énumérations et de structures polluent le code plus que toute autre chose.
Le résultat est un peu:
Comme vous le voyez, votre énumération pollue l'espace de noms global. Si vous mettez cette énumération dans un espace de noms, vous aurez quelque chose comme:
extern const int?
Si vous souhaitez diminuer le couplage (c'est-à-dire pouvoir masquer les valeurs des constantes, et ainsi les modifier à votre guise sans avoir besoin d'une recompilation complète), vous pouvez déclarer les entiers comme extern dans l'en-tête, et comme constante dans le fichier CPP , comme dans l'exemple suivant:
Et:
Cependant, vous ne pourrez pas utiliser le commutateur sur ces constantes. Alors à la fin, choisissez votre poison ... :-p
la source
Avez-vous exclu std :: bitset? Les ensembles de drapeaux sont ce à quoi il sert. Faire
puis
Puisqu'il y a un tas de surcharges d'opérateurs pour l'ensemble de bits, vous pouvez maintenant faire
Ou quelque chose de très similaire à cela - j'apprécierais toutes les corrections puisque je ne l'ai pas testé. Vous pouvez également faire référence aux bits par index, mais il est généralement préférable de ne définir qu'un seul ensemble de constantes, et les constantes RecordType sont probablement plus utiles.
En supposant que vous ayez exclu le bitet, je vote pour l' énumération .
Je n'achète pas que lancer les énumérations est un sérieux inconvénient - OK, donc c'est un peu bruyant, et attribuer une valeur hors plage à une énumération est un comportement indéfini, il est donc théoriquement possible de se tirer une balle dans le pied sur un C ++ inhabituel implémentations. Mais si vous ne le faites que lorsque cela est nécessaire (c'est-à-dire lorsque vous passez de int à enum iirc), c'est du code parfaitement normal que les gens ont déjà vu.
Je doute également du coût d'espace de l'énumération. Les variables et paramètres uint8 n'utiliseront probablement pas moins de stack que ints, donc seul le stockage dans les classes compte. Il y a des cas où l'empaquetage de plusieurs octets dans une structure gagnera (dans ce cas, vous pouvez lancer des énumérations dans et hors du stockage uint8), mais normalement le remplissage tuera l'avantage de toute façon.
Ainsi, l'énumération n'a pas d'inconvénients par rapport aux autres, et comme avantage, vous donne un peu de sécurité de type (vous ne pouvez pas attribuer une valeur entière aléatoire sans lancer explicitement un cast) et des moyens propres de faire référence à tout.
De préférence, je mettrais également le "= 2" dans l'énumération, d'ailleurs. Ce n'est pas nécessaire, mais un "principe de moindre étonnement" suggère que les 4 définitions devraient se ressembler.
la source
bitset
pour cela? cela se traduit généralement par unlong
(dans mon implémentation iirc; oui, quel gaspillage) ou un type intégral similaire pour chaque élément de toute façon, alors pourquoi ne pas simplement utiliser des intégrales non masquées? (ou, de nos jours,constexpr
sans stockage)bitset
classe, à part ce qui semble être un courant sous-jacent récurrent dans les discussions environnantes sur 'ugh, nous devons couvrir les racines de bas niveau peu recommandables de la langue 'uint8
variables et les paramètres n'utiliseront probablement pas moins de pile queints
" est faux. Si vous avez un processeur avec des registres 8 bits, vous avezint
besoin d'au moins 2 registres alorsuint8_t
que vous n'en avez besoin que de 1, vous aurez donc besoin de plus d'espace dans la pile car vous êtes plus susceptible d'être à court de registres (ce qui est également plus lent et peut augmenter la taille du code ( selon le jeu d'instructions)). (Vous avez un type, ça ne devraituint8_t
pas êtreuint8
)Voici quelques articles sur const vs macros vs enums:
Constantes symboliques Constantes d'
énumération et objets constants
Je pense que vous devriez éviter les macros d'autant plus que vous avez écrit la plupart de votre nouveau code en C ++ moderne.
la source
Si possible, n'utilisez PAS de macros. Ils ne sont pas trop admirés en ce qui concerne le C ++ moderne.
la source
Les énumérations seraient plus appropriées car elles fournissent une "signification aux identificateurs" ainsi qu'une sécurité de type. Vous pouvez clairement dire que "xDeleted" est de "RecordType" et que cela représente un "type d'enregistrement" (wow!) Même après des années. Les consts auraient besoin de commentaires pour cela, ainsi que de monter et descendre dans le code.
la source
Pas nécessairement...
Pas nécessairement - mais vous devez être explicite aux points de stockage ...
Vous pouvez créer des opérateurs pour soulager cela:
La même chose peut arriver avec n'importe lequel de ces mécanismes: les vérifications de plage et de valeur sont normalement orthogonales à la sécurité de type (bien que les types définis par l'utilisateur - c'est-à-dire vos propres classes - peuvent imposer des "invariants" à leurs données). Avec les énumérations, le compilateur est libre de choisir un type plus grand pour héberger les valeurs, et une variable d'énumération non initialisée, corrompue ou simplement mal réglée pourrait encore finir par interpréter son modèle de bits comme un nombre auquel vous ne vous attendez pas - en comparant inégal à l'un des les identificateurs d'énumération, toute combinaison d'entre eux, et 0.
Eh bien, à la fin, le OU bit à bit de style C éprouvé des énumérations fonctionne plutôt bien une fois que vous avez des champs de bits et des opérateurs personnalisés dans l'image. Vous pouvez encore améliorer votre robustesse avec des fonctions de validation et des assertions personnalisées comme dans la réponse de mat_geek; techniques souvent également applicables à la gestion des chaînes, des int, des valeurs doubles, etc.
Vous pourriez dire que c'est «plus propre»:
Je suis indifférent: les bits de données sont plus serrés mais le code augmente considérablement ... dépend du nombre d'objets que vous avez, et les lamdbas - aussi beaux soient-ils - sont toujours plus désordonnés et plus difficiles à obtenir que les OU au niveau du bit.
BTW / - l'argument sur la assez faible IMHO de la sécurité des threads - mieux connu comme une considération de fond plutôt que de devenir une force motrice de décision dominante; partager un mutex à travers les champs de bits est une pratique plus probable même si vous ne connaissez pas leur emballage (les mutex sont des membres de données relativement volumineux - je dois vraiment me préoccuper des performances pour envisager d'avoir plusieurs mutex sur les membres d'un même objet, et je regarderais attentivement assez pour remarquer qu'ils étaient des champs de bits). Tout type de taille de sous-mot pourrait avoir le même problème (par exemple a
uint8_t
). Quoi qu'il en soit, vous pouvez essayer des opérations de style comparaison et échange atomiques si vous êtes désespéré pour une plus grande concurrence.la source
operator|
doit être converti en un type entier (unsigned int
) avant l'instruction|
. Sinon, leoperator|
s'appellera récursivement et provoquera un débordement de pile au moment de l'exécution. Je suggère:return RecordType( unsigned(lhs) | unsigned(rhs) );
. CheersMême si vous devez utiliser 4 octets pour stocker une énumération (je ne suis pas très familier avec C ++ - je sais que vous pouvez spécifier le type sous-jacent en C #), cela en vaut toujours la peine - utilisez des énumérations.
À l'heure actuelle des serveurs avec Go de mémoire, des choses comme 4 octets contre 1 octet de mémoire au niveau de l'application en général n'ont pas d'importance. Bien sûr, si dans votre situation particulière, l'utilisation de la mémoire est si importante (et que vous ne pouvez pas faire en sorte que C ++ utilise un octet pour sauvegarder l'énumération), alors vous pouvez envisager la route 'static const'.
À la fin de la journée, vous devez vous demander, est-ce que cela vaut la peine d'utiliser «static const» pour les 3 octets d'économie de mémoire pour votre structure de données?
Autre chose à garder à l'esprit - IIRC, sur x86, les structures de données sont alignées sur 4 octets, donc à moins que vous n'ayez un certain nombre d'éléments de largeur d'octet dans votre structure «enregistrement», cela n'a peut-être pas vraiment d'importance. Testez et assurez-vous que c'est le cas avant de faire un compromis entre la maintenabilité et la performance / l'espace.
la source
int
moins que ce ne soit trop petit". [Si vous ne spécifiez pas le type sous-jacent dans C ++ 11, il utilise un comportement hérité. Inversement, leenum class
type sous-jacent de C ++ 11 prend explicitement la valeur par défaut, saufint
indication contraire.]Si vous voulez la sécurité de type des classes, avec la commodité de la syntaxe d'énumération et de la vérification des bits, considérez les étiquettes sécurisées en C ++ . J'ai travaillé avec l'auteur et il est assez intelligent.
Attention cependant. Au final, ce package utilise des modèles et des macros!
la source
Avez-vous réellement besoin de transmettre les valeurs d'indicateur dans leur ensemble conceptuel ou allez-vous avoir beaucoup de code par indicateur? Quoi qu'il en soit, je pense qu'avoir ceci comme classe ou structure de champs de bits 1 bit pourrait en fait être plus clair:
Ensuite, votre classe d'enregistrement peut avoir une variable membre struct RecordFlag, les fonctions peuvent prendre des arguments de type struct RecordFlag, etc. Le compilateur doit regrouper les champs de bits, économisant de l'espace.
la source
Je n'utiliserais probablement pas une énumération pour ce genre de chose où les valeurs peuvent être combinées ensemble, plus généralement les énumérations sont des états mutuellement exclusifs.
Mais quelle que soit la méthode que vous utilisez, pour indiquer plus clairement que ce sont des valeurs qui sont des bits qui peuvent être combinés ensemble, utilisez plutôt cette syntaxe pour les valeurs réelles:
L'utilisation d'un décalage vers la gauche permet d'indiquer que chaque valeur est destinée à être un seul bit, il est moins probable que plus tard quelqu'un fasse quelque chose de mal comme ajouter une nouvelle valeur et lui attribuer une valeur de 9.
la source
Basé sur KISS , haute cohésion et faible couplage , posez ces questions -
Il existe un excellent livre " Large-Scale C ++ Software Design ", qui promeut les types de base à l'extérieur, si vous pouvez éviter une autre dépendance de fichier d'en-tête / d'interface, vous devriez essayer.
la source
Si vous utilisez Qt, vous devriez jeter un œil aux QFlags . La classe QFlags fournit un moyen sûr de stocker des combinaisons OR de valeurs enum.
la source
Je préfère aller avec
Simplement parce que:
la source
Non pas que j'aime tout sur-concevoir, mais parfois dans ces cas, il peut être utile de créer une (petite) classe pour encapsuler ces informations. Si vous créez une classe RecordType, elle peut avoir des fonctions telles que:
void setDeleted ();
void clearDeleted ();
booléen isDeleted ();
etc ... (ou tout ce qui convient à la convention)
Il pourrait valider les combinaisons (dans le cas où toutes les combinaisons ne sont pas légales, par exemple si «nouveau» et «supprimé» ne peuvent pas être définis en même temps). Si vous venez d'utiliser des masques de bits, etc., le code qui définit l'état doit être validé, une classe peut également encapsuler cette logique.
La classe peut également vous donner la possibilité d'attacher des informations de journalisation significatives à chaque état, vous pouvez ajouter une fonction pour renvoyer une représentation sous forme de chaîne de l'état actuel, etc. (ou utiliser les opérateurs de streaming '<<').
Pour autant, si vous êtes préoccupé par le stockage, vous pourriez toujours avoir la classe uniquement avec un membre de données 'char', donc ne prenez qu'une petite quantité de stockage (en supposant qu'il ne soit pas virtuel). Bien sûr, selon le matériel, etc., vous pouvez avoir des problèmes d'alignement.
Vous pourriez avoir les valeurs de bits réelles non visibles par le reste du «monde» si elles sont dans un espace de noms anonyme à l'intérieur du fichier cpp plutôt que dans le fichier d'en-tête.
Si vous trouvez que le code utilisant l'énumération / # define / bitmask etc a beaucoup de code de «support» pour traiter les combinaisons invalides, la journalisation, etc. alors l'encapsulation dans une classe peut valoir la peine d'être considérée. Bien sûr, la plupart du temps, les problèmes simples sont mieux lotis avec des solutions simples ...
la source