Dois-je utiliser #define, enum ou const?


125

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 enumje perds de l'espace (entiers) et je dois probablement effectuer un cast quand je veux faire une opération au niveau du bit. Avec constje pense que je perds aussi la sécurité de type car un aléatoire uint8pourrait 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 #defines, 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.


"avec enum je perds de l'espace (entiers)". Pas nécessairement. Voir stackoverflow.com/questions/366017/… et stackoverflow.com/questions/1113855/… (et -fshort-enum de gcc . (Je suppose que ces réponses C sont toujours vraies en C ++.)
idbrii

@pydave Si vous n'êtes pas sûr de la compatibilité de C et C ++, je trouve ce lien très utile, voir par exemple enum david.tribble.com/text/cdiffs.htm#C99-enum-type
aka.nice

3
Ceci est un sujet plus ancien avec des votes élevés, y a-t-il une raison de ne pas mentionner les classes enum C ++ 11 pour cette situation problématique.
Brandin

À noter, enum RecordType : uint8_tcombine le type de sécurité deenum avec la petite taille de uint8_t, bien que vous deviez toujours fournir des opérateurs au niveau du bit.
Justin Time - Réintègre Monica

Réponses:


88

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.

namespace RecordType {

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 ++.

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

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.

xInvalid = 16 };

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 letypedef est juste un intet une valeur telle que 0xDEADBEEFpeut être dans votre variable via des variables non initialisées ou mal pointées.

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

Ajouter un using directive si vous souhaitez utiliser le type souvent.

using RecordType ::TRecordType ;

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.

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

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.


1
Surtout une bonne réponse - mais la question stipule que les indicateurs peuvent être combinés et la fonction IsValidState () ne permet pas de les combiner.
Jonathan Leffler

3
@Jonathan Leffler: d'où je suis, je pense que «IsValidState» n'est pas censé faire ça, «IsValidMask» l'est.
João Portela du

1
Est-il souhaité que IsValidMaskne permette pas de sélectionner aucun (ie 0)?
Joachim Sauer

2
−1 L'idée de la vérification de type à l'exécution est une abomination.
Bravo et hth. - Alf

54

Oubliez les définitions

Ils pollueront votre code.

bitfields?

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

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.

Cependant, les membres bit dans les structures présentent des inconvénients pratiques. Premièrement, l'ordre des bits en mémoire varie d'un compilateur à l'autre. De plus, de nombreux compilateurs populaires génèrent du code inefficace pour la lecture et l'écriture de membres de bits , et il existe des sécurité des threads liés aux champs de bits (en particulier sur les systèmes multiprocesseurs) en raison du fait que la plupart des machines ne peuvent pas manipuler des ensembles arbitraires de bits en mémoire, mais doit à la place charger et stocker des mots entiers. par exemple ce qui suit ne serait pas thread-safe, malgré l'utilisation d'un mutex

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?

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

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:

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

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.

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

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:

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

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

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:

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?

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:

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

Et:

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

Cependant, vous ne pourrez pas utiliser le commutateur sur ces constantes. Alors à la fin, choisissez votre poison ... :-p


5
Pourquoi pensez-vous que les champs de bits sont lents? Avez-vous réellement profilé du code en l'utilisant et une autre méthode? Même si c'est le cas, la clarté peut être plus importante que la vitesse, ce qui simplifie un peu «ne jamais utiliser ça».
wnoise le

"static const uint8 xNew;" n'est redondant que parce que dans C ++ const, les variables d'étendue de l'espace de noms utilisent par défaut la liaison interne. Supprimez "const" et il a un lien externe. Aussi, "enum {...} RecordType;" déclare une variable globale nommée "RecordType" dont le type est une énumération anonyme.
bk1e

onebyone: Premièrement, la raison principale était que le gain (quelques octets, le cas échéant) était surchargé par la perte (plus lent à accéder, à la fois en lecture et en écriture) ...
paercebal

3
onebyone: Deuxièmement, tout le code que je produis au travail ou à la maison est intrinsèquement thread-safe. C'est facile à faire: pas de globaux, pas de statique, pas partagé entre les threads à moins d'être protégé par un verrou. Utiliser cet idiome briserait cette sécurité de base des threads. Et pour quoi? Quelques octets peut - être ? ... :-) ...
paercebal

Ajout d'une référence à l'article de Raymond Chen sur les coûts cachés des champs de bits.
paercebal

30

Avez-vous exclu std :: bitset? Les ensembles de drapeaux sont ce à quoi il sert. Faire

typedef std::bitset<4> RecordType;

puis

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

Puisqu'il y a un tas de surcharges d'opérateurs pour l'ensemble de bits, vous pouvez maintenant faire

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

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.


1
En fait, je n'ai pas du tout envisagé de bitset. Cependant, je ne suis pas sûr que ce serait bien. Avec l'ensemble de bits, je dois adresser les bits comme 1, 2, 3, 4, ce qui rendrait le code moins lisible - ce qui signifie que j'utiliserais probablement une énumération pour `` nommer '' les bits. Cela pourrait cependant être un gain de place. Merci.
Milan Babuškov

Milan, vous n'avez pas à "nommer" les bits en utilisant une énumération, vous pouvez simplement utiliser les bits prédéfinis comme indiqué ci-dessus. Si vous voulez activer le bit un, plutôt que my_bitset.flip (1), vous feriez my_bitset | = xNew;
moswald le

cela s'adresse moins à vous qu'à la STL, mais: je dois vraiment vous demander: pourquoi l'utiliseriez-vous bitsetpour cela? cela se traduit généralement par un long(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, constexprsans stockage)
underscore_d

[edit timeout] ... mais je n'ai jamais vraiment compris la raison d'être de la bitsetclasse, à 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 '
underscore_d

"les uint8variables et les paramètres n'utiliseront probablement pas moins de pile que ints" est faux. Si vous avez un processeur avec des registres 8 bits, vous avez intbesoin d'au moins 2 registres alors uint8_tque 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 devrait uint8_tpas être uint8)
12431234123412341234123


5

Si possible, n'utilisez PAS de macros. Ils ne sont pas trop admirés en ce qui concerne le C ++ moderne.


4
Vrai. Ce que je déteste moi-même à propos des macros, c'est que vous ne pouvez pas y entrer si elles sont erronées.
Carl

J'imagine que c'est quelque chose qui pourrait être corrigé dans le compilateur.
celticminstrel

4

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.


4

Avec définit je perds la sécurité de type

Pas nécessairement...

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

et avec enum je perds de l'espace (entiers)

Pas nécessairement - mais vous devez être explicite aux points de stockage ...

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

et dois probablement lancer lorsque je veux faire une opération au niveau du bit.

Vous pouvez créer des opérateurs pour soulager cela:

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

Avec const, je pense que je perds également la sécurité de type car un uint8 aléatoire pourrait entrer par erreur.

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.

Existe-t-il un autre moyen plus propre? / Sinon, qu'utiliseriez-vous et pourquoi?

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»:

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

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

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.


1
+1 Amende. Mais le operator|doit être converti en un type entier ( unsigned int) avant l'instruction |. Sinon, le operator|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) );. Cheers
olibre

3

Mê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.


Vous pouvez spécifier le type sous-jacent en C ++, à partir de la révision du langage C ++ 11. Jusque-là, je crois qu'il était "au moins assez grand pour stocker et être utilisé comme champ de bits pour tous les énumérateurs spécifiés, mais probablement à intmoins 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, le enum classtype sous-jacent de C ++ 11 prend explicitement la valeur par défaut, sauf intindication contraire.]
Justin Time - Réintégrer Monica

3

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!


On dirait exagéré pour ma petite application. mais cela semble être une bonne solution.
Milan Babuškov le

2

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:

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

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.


Parfois dans son ensemble, parfois comme drapeau. Et, j'ai également besoin de tester si un certain drapeau est défini (lorsque je le passe dans son ensemble).
Milan Babuškov

Eh bien, lorsqu'ils sont séparés, demandez simplement un int. Lorsqu'ils sont ensemble, passez la structure.
wnoise

Ce ne sera pas mieux. L'accès aux champs de bits est plus lent qu'autre chose.
paercebal

Vraiment? Vous pensez que le compilateur générera un code significativement différent pour tester les champs de bits par rapport au twiddling manuel? Et que ce sera nettement plus lent? Pourquoi? La seule chose que vous ne pouvez pas faire de manière idiomatique est de masquer plusieurs indicateurs à la fois.
wnoise

En exécutant un test de lecture simple, j'obtiens 5,50-5,58 secondes pour le masquage de bits contre 5,45-5,59 pour l'accès au champ de bits. Presque indiscernable.
wnoise le

2

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:

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

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.


1
Il existe suffisamment de précédents pour cela, en particulier dans les constantes pour ioctl (). Je préfère utiliser des constantes hexadécimales, cependant: 0x01, 0x02, 0x04, 0x08, 0x10, ...
Jonathan Leffler

2

Basé sur KISS , haute cohésion et faible couplage , posez ces questions -

  • Qui a besoin de savoir? ma classe, ma bibliothèque, d'autres classes, d'autres bibliothèques, des tiers
  • Quel niveau d'abstraction dois-je fournir? Le consommateur comprend-il les opérations sur les bits.
  • Devrai-je m'interfacer depuis VB / C # etc?

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.


1
a) 5-6 classes. b) seulement moi, c'est un projet
individuel

2

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.


Non, pas de Qt. En fait, c'est un projet wxWidgets.
Milan Babuškov le

0

Je préfère aller avec

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

Simplement parce que:

  1. Il est plus propre et rend le code lisible et maintenable.
  2. Il regroupe logiquement les constantes.
  3. Le temps du programmeur est plus important, sauf si votre travail consiste à enregistrer ces 3 octets.

Eh bien, je pourrais facilement avoir un million d'instances de la classe Record, donc cela pourrait être important. OTOH, c'est juste une différence entre 1 Mo et 4 Mo, alors peut-être que je ne devrais pas m'inquiéter.
Milan Babuškov

@Vivek: Avez-vous envisagé la limitation de la largeur des entiers? En particulier avant C ++ 11.
user2672165

0

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


Malheureusement, la déclaration doit être dans un fichier .h car elle est utilisée dans tout le projet (utilisée par 5 à 6 classes).
Milan Babuškov
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.