espaces de noms pour les types enum – meilleures pratiques

Souvent, il faut plusieurs types énumérés ensemble. Parfois, on a un conflit de nom. On pense à deux solutions: utiliser un espace de noms ou utiliser des noms d’élément enum plus grands. Cependant, la solution d’espace de noms a deux implémentations possibles: une classe factice avec enum nested ou un espace de noms complet.

Je cherche les avantages et les inconvénients des trois approches.

Exemple:

// oft seen hand-crafted name clash solution enum eColors { cRed, cColorBlue, cGreen, cYellow, cColorsEnd }; enum eFeelings { cAngry, cFeelingBlue, cHappy, cFeelingsEnd }; void setPenColor( const eColors c ) { switch (c) { default: assert(false); break; case cRed: //... break; case cColorBlue: //... //... } } // (ab)using a class as a namespace class Colors { enum e { cRed, cBlue, cGreen, cYellow, cEnd }; }; class Feelings { enum e { cAngry, cBlue, cHappy, cEnd }; }; void setPenColor( const Colors::ec ) { switch (c) { default: assert(false); break; case Colors::cRed: //... break; case Colors::cBlue: //... //... } } // a real namespace? namespace Colors { enum e { cRed, cBlue, cGreen, cYellow, cEnd }; }; namespace Feelings { enum e { cAngry, cBlue, cHappy, cEnd }; }; void setPenColor( const Colors::ec ) { switch (c) { default: assert(false); break; case Colors::cRed: //... break; case Colors::cBlue: //... //... } } 

    Original C ++ 03 answer:

    L’ avantage d’un namespace (sur une class ) est que vous pouvez utiliser using déclarations lorsque vous le souhaitez.

    Le problème avec l’utilisation d’un namespace est que les espaces de noms peuvent être étendus ailleurs dans le code. Dans un projet de grande envergure, vous ne seriez pas eFeelings que deux énumérations distinctes ne pensent pas toutes deux qu’elles sont appelées eFeelings

    Pour un code plus simple, j’utilise une struct , car vous voulez probablement que le contenu soit public.

    Si vous faites l’une de ces pratiques, vous êtes en avance sur la courbe et n’avez probablement pas besoin de l’examiner plus avant.

    Conseil plus récent, C ++ 11:

    Si vous utilisez C ++ 11 ou une version ultérieure, la enum class implicitement les valeurs d’énumération dans le nom de l’énumération.

    Avec la enum class vous perdrez des conversions et des comparaisons implicites avec des types entiers, mais dans la pratique, cela peut vous aider à marquer un code ambigu ou bogué.

    FYI Dans C ++ 0x, il y a une nouvelle syntaxe pour les cas comme ce que vous avez mentionné (voir la page wiki C ++ 0x )

     enum class eColors { ... }; enum class eFeelings { ... }; 

    J’éviterais certainement d’utiliser une classe pour cela; utilisez plutôt un espace de noms. La question se résume à savoir si utiliser un espace de noms ou utiliser des identifiants uniques pour les valeurs enum. Personnellement, j’utiliserais un espace de noms pour que mes identifiants soient plus courts et, espérons-le, plus explicites. Ensuite, le code de l’application pourrait utiliser une directive “using namespace” et rendre tout plus lisible.

    De votre exemple ci-dessus:

     using namespace Colors; void setPenColor( const ec ) { switch (c) { default: assert(false); break; case cRed: //... break; case cBlue: //... //... } } 

    J’ai hybridé les réponses précédentes à quelque chose comme ceci: (EDIT: Ceci n’est utile que pour pre-C ++ 11. Si vous utilisez C ++ 11, utilisez la enum class )

    J’ai un gros fichier d’en-tête qui contient tous les chiffres de mon projet, car ces énumérations sont partagées entre les classes de travail et il n’est pas logique de mettre les énumérations dans les classes de travail elles-mêmes.

    La struct évite le public: le sucre syntaxique, et le typedef vous permet de déclarer les variables de ces énumérations dans les autres classes de travail.

    Je ne pense pas que l’utilisation d’un espace de noms aide du tout. Peut-être est-ce parce que je suis un programmeur C #, et que vous devez utiliser le nom du type enum lorsque vous faites référence aux valeurs, alors je suis habitué.

      struct KeySource { typedef enum { None, Efuse, Bbram } Type; }; struct Checksum { typedef enum { None =0, MD5 = 1, SHA1 = 2, SHA2 = 3 } Type; }; struct Encryption { typedef enum { Undetermined, None, AES } Type; }; struct File { typedef enum { Unknown = 0, MCS, MEM, BIN, HEX } Type; }; 

     class Worker { File::Type fileType; void DoIt() { switch(fileType) { case File::MCS: ... ; case File::MEM: ... ; case File::HEX: ... ; } } 

    Une différence entre l’utilisation d’une classe ou d’un espace de noms est que la classe ne peut pas être rouverte comme un espace de noms. Cela évite la possibilité que l’espace de noms soit utilisé à l’avenir, mais il y a également le problème que vous ne pouvez pas append à l’ensemble des énumérations.

    Un avantage possible pour l’utilisation d’une classe est qu’ils peuvent être utilisés comme arguments de type modèle, ce qui n’est pas le cas pour les espaces de noms:

     class Colors { public: enum TYPE { Red, Green, Blue }; }; template  void foo (T t) { typedef typename T::TYPE EnumType; // ... } 

    Personnellement, je ne suis pas un fan de l’ utilisation , et je préfère les noms complets, donc je ne vois pas vraiment cela comme un avantage pour les espaces de noms. Cependant, ce n’est probablement pas la décision la plus importante que vous prendrez dans votre projet!

    L’avantage d’utiliser une classe est que vous pouvez construire une classe à part entière.

     #include  class Color { public: typedef enum { Red, Blue, Green, Yellow } enum_type; private: enum_type _val; public: Color(enum_type val = Blue) : _val(val) { assert(val < = Yellow); } operator enum_type() const { return _val; } }; void SetPenColor(const Color c) { switch (c) { case Color::Red: // ... break; } } 

    Comme le montre l'exemple ci-dessus, en utilisant une classe, vous pouvez:

    1. interdire (malheureusement, pas la compilation) le C ++ d’autoriser une conversion de valeur invalide,
    2. définir une valeur par défaut (non nulle) pour les énumérations nouvellement créées,
    3. append d'autres méthodes, comme pour renvoyer une représentation sous forme de chaîne d'un choix.

    Notez simplement que vous devez déclarer l' operator enum_type() pour que C ++ sache comment convertir votre classe en enum sous-jacent. Sinon, vous ne pourrez pas transmettre le type à une instruction switch .

    Étant donné que les énumérations portent sur leur scope, il est probablement préférable de les inclure dans quelque chose pour éviter de polluer l’espace de noms global et d’éviter les collisions de noms. Je préfère un espace de nommage à la classe simplement parce que l’ namespace ressemble à un sac de maintien, alors que la class ressemble à un object robuste (cf. le débat struct vs class ). Un avantage possible pour un espace de noms est qu’il peut être étendu plus tard – utile si vous traitez avec du code tiers que vous ne pouvez pas modifier.

    C’est tout à fait évident quand on obtient des classes enum avec C ++ 0x.

    J’ai aussi tendance à envelopper mes énumérations dans les classes.

    Comme l’indique Richard Corden, l’avantage d’une classe est qu’elle est un type au sens c ++ et que vous pouvez donc l’utiliser avec des modèles.

    J’ai une boîte à outils spéciale :: Enum classe pour mes besoins que je spécialise pour chaque modèle qui fournit des fonctions de base (principalement: mapper une valeur enum à un std :: ssortingng pour que les E / S soient plus faciles à lire).

    Mon petit modèle a également l’avantage supplémentaire de vérifier les valeurs autorisées. Le compilateur est un peu laxiste sur le fait de vérifier si la valeur est vraiment dans l’énumération:

     typedef enum { False: 0, True: 2 } boolean; // The classic enum you don't want to see around your code ;) int main(int argc, char* argv[]) { boolean x = static_cast(1); return (x == False || x == True) ? 0 : 1; } // main 

    Cela m’a toujours dérangé que le compilateur n’attrape pas ceci, puisque vous avez une valeur enum qui n’a aucun sens (et à laquelle vous ne vous attendez pas).

    De même:

     typedef enum { Zero: 0, One: 1, Two: 2 } example; int main(int argc, char* argv[]) { example y = static_cast(3); return (y == Zero || y == One || y == Two) ? 0 : 1; } // main 

    Encore une fois, main retournera une erreur.

    Le problème est que le compilateur s’adaptera à l’énumération dans la plus petite représentation disponible (il faut ici 2 bits) et que tout ce qui rentre dans cette représentation est considéré comme une valeur valide.

    Il y a aussi le problème que parfois vous préféreriez avoir une boucle sur les valeurs possibles au lieu d’un commutateur afin de ne pas avoir à modifier tout ce que vous changez chaque fois que vous ajoutez une valeur à l’énumération.

    Tout compte fait, mon petit assistant facilite vraiment les choses pour mes énumérations (bien sûr, cela ajoute un peu de surcharge) et ce n’est possible que parce que je niche chaque enum dans sa propre structure 🙂