espaces de noms pour les types enum - bonnes pratiques

102

Souvent, il faut plusieurs types énumérés ensemble. Parfois, on a un conflit de nom. Deux solutions me viennent à l'esprit: utilisez un espace de noms ou utilisez des noms d'élément d'énumération «plus grands». Pourtant, la solution d'espace de noms a deux implémentations possibles: une classe factice avec une énumération imbriquée ou un espace de noms complet.

Je recherche 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::e c ) {
    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::e c ) {
    switch (c) {
        default: assert(false);
        break; case Colors::cRed: //...
        break; case Colors::cBlue: //...
        //...
    }
  }
xtofl
la source
19
Tout d'abord, j'utiliserais Color :: Red, Feeling: Angry, etc
abatishchev
bonne question, j'ai utilisé la méthode de l'espace de noms ....;)
MiniScalope
18
le préfixe "c" sur tout nuit à la lisibilité.
Utilisateur
8
@User: pourquoi, merci d'avoir ouvert la discussion hongroise :)
xtofl
5
Notez que vous n'avez pas besoin de nommer l'énumération comme dans enum e {...}, les énumérations peuvent être anonymes, c'est enum {...}-à- dire , ce qui a beaucoup plus de sens lorsqu'elles sont enveloppées dans un espace de noms ou une classe.
kralyk

Réponses:

73

Réponse originale C ++ 03:

L' avantage d'un namespace(sur a class) est que vous pouvez utiliser des usingdéclarations quand vous le souhaitez.

Le problème avec l'utilisation de a namespaceest que les espaces de noms peuvent être développés ailleurs dans le code. Dans un grand projet, vous ne seriez pas assuré que deux énumérations distinctes ne pensent pas toutes deux qu'elles sont appeléeseFeelings

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

Si vous pratiquez l'une de ces pratiques, vous êtes en avance sur la courbe et n'avez probablement pas besoin d'examiner cela plus en détail.

Plus récent, conseil C ++ 11:

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

Avec, enum classvous perdrez les conversions implicites et les comparaisons avec les types entiers, mais en pratique, cela peut vous aider à découvrir du code ambigu ou bogué.

Drew Dormann
la source
4
Je suis d'accord avec l'idée de structure. Et merci pour le compliment :)
xtofl
3
+1 Je ne me souvenais pas de la syntaxe C ++ 11 "enum class". Sans cette fonctionnalité, les énumérations sont incomplètes.
Grault
Est-ce leur une option pour utiliser "using" pour la portée implicite de la "classe enum". Par exemple, ajoutera "en utilisant Color :: e;" pour coder autoriser l'utilisation de 'cRed' et savoir que cela devrait être Color :: e :: cRed?
JVApen le
20

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

enum class eColors { ... };
enum class eFeelings { ... };
jmihalicza
la source
11

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

J'ai un gros fichier d'en-tête qui contient toutes les énumérations de mon projet, car ces énumérations sont partagées entre les classes de travail et cela n'a pas de sens de mettre les énumérations dans les classes de travail elles-mêmes.

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

Je ne pense pas que l'utilisation d'un espace de noms aide du tout. C'est peut-être parce que je suis un programmeur C #, et là, vous devez utiliser le nom de type enum pour référencer les valeurs, donc 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: ... ;
    }
}
Mark Lakata
la source
9

J'éviterais certainement d'utiliser une classe pour cela; utilisez plutôt un espace de noms. La question se résume à savoir s'il faut utiliser un espace de noms ou utiliser des identifiants uniques pour les valeurs d'énumération. 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.

D'après votre exemple ci-dessus:

using namespace Colors;

void setPenColor( const e c ) {
    switch (c) {
        default: assert(false);
        break; case cRed: //...
        break; case cBlue: //...
        //...
    }
}
Charles Anderson
la source
Pouvez-vous nous expliquer pourquoi vous préféreriez un espace de noms à une classe?
xtofl
@xtofl: vous ne pouvez pas écrire 'using class Colors`
MSalters
2
@MSalters: Vous ne pouvez pas non plus écrire Colors someColor = Red;, car l'espace de noms ne constitue pas un type. Vous auriez à écrire à la Colors::e someColor = Red;place, ce qui est assez contre-intuitif.
SasQ
@SasQ N'auriez-vous pas à l'utiliser Colors::e someColormême avec un struct/classsi vous vouliez l'utiliser dans une switchdéclaration? Si vous utilisez un anonyme, enumle commutateur ne pourra pas évaluer un fichier struct.
Macbeth's Enigma
1
Désolé, mais const e cme semble difficilement lisible :-) Ne fais pas ça. L'utilisation d'un espace de noms est cependant très bien.
dhaumann le
7

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 peut. Cela évite la possibilité que l'espace de noms puisse être abusé à l'avenir, mais il y a aussi le problème que vous ne pouvez pas non plus ajouter à l'ensemble des énumérations.

Un avantage possible de l'utilisation d'une classe est qu'elle peut être utilisée 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 <typename T> void foo (T t) {
  typedef typename T::TYPE EnumType;
  // ...
}

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

Richard Corden
la source
Ne pas rouvrir les classes est également un inconvénient potentiel. La liste des couleurs n'est pas non plus finie.
MSalters
1
Je pense que ne pas rempoter une classe est un avantage potentiel. Si je veux plus de couleurs, je recompilerais simplement la classe avec plus de couleurs. Si je ne peux pas faire cela (disons que je n'ai pas le code), alors je ne veux en aucun cas y toucher.
Thomas Eding
@MSalters: Aucune possibilité de rouvrir une classe n'est non seulement un inconvénient, mais aussi un outil de sécurité. Parce que lorsqu'il serait possible de rouvrir une classe et d'ajouter des valeurs à l'énumération, cela pourrait casser un autre code de bibliothèque qui dépend déjà de cette énumération et ne connaît que l'ancien ensemble de valeurs. Il accepterait alors volontiers ces nouvelles valeurs, mais s'interdirait au moment de l'exécution de ne pas savoir quoi en faire. N'oubliez pas le principe ouvert-fermé: la classe doit être fermée pour modification , mais ouverte pour extension . En étendant, je veux dire ne pas ajouter au code existant, mais l'envelopper avec le nouveau code (par exemple, dériver).
SasQ
Ainsi, lorsque vous souhaitez étendre l'énumération, vous devez en faire un nouveau type dérivé du premier (si seulement cela était facilement possible en C ++ ...; /). Ensuite, il pourrait être utilisé en toute sécurité par le nouveau code qui comprend ces nouvelles valeurs, mais seules les anciennes valeurs seraient acceptées (en les convertissant vers le bas) par l'ancien code. Ils ne devraient accepter aucune de ces nouvelles valeurs comme étant du mauvais type (la plus étendue). Seules les anciennes valeurs qu'ils comprennent sont acceptées comme étant du type correct (de base) (et étant accidentellement également du nouveau type, donc elles pourraient également être acceptées par un nouveau code).
SasQ
7

L'avantage d'utiliser une classe est que vous pouvez créer une classe à part entière par-dessus.

#include <cassert>

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) au C ++ d'autoriser une conversion à partir d'une valeur non valide,
  2. définir une valeur par défaut (différente de zéro) pour les énumérations nouvellement créées,
  3. ajoutez 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 operator enum_type()pour que C ++ sache comment convertir votre classe en énumération sous-jacente. Sinon, vous ne pourrez pas transmettre le type à une switchinstruction.

Michał Górny
la source
Cette solution est-elle en quelque sorte liée à ce qui est montré ici?: En.wikibooks.org/wiki/More_C%2B%2B_Idioms/Type_Safe_Enum Je réfléchis à comment en faire un modèle que je n'ai pas à réécrire ce modèle à chaque fois J'ai besoin de l'utiliser.
SasQ
@SasQ: ça semble similaire, oui. C'est probablement la même idée. Cependant, je ne suis pas sûr qu'un modèle soit bénéfique à moins que vous n'y ajoutiez beaucoup de méthodes «courantes».
Michał Górny
1. Pas vraiment vrai. Vous pouvez demander à la compilation de vérifier que l'énumération est valide, via const_expr ou via un constructeur privé pour un int.
xryl669
5

Puisque les énumérations sont étendues à leur portée englobante, il est probablement préférable de les envelopper dans quelque chose pour éviter de polluer l'espace de noms global et pour éviter les collisions de noms. Je préfère un espace de noms à la classe simplement parce que cela namespaceressemble à un sac de maintien, alors que je classme sens comme un objet robuste (cf. le débat structvs class). Un avantage possible d'un espace de noms est qu'il peut être étendu plus tard - utile si vous avez affaire à du code tiers que vous ne pouvez pas modifier.

Tout cela est bien sûr inutile lorsque nous obtenons des classes enum avec C ++ 0x.

Michael Kristofik
la source
enum classes ... besoin de chercher ça!
xtofl
3

J'ai aussi tendance à emballer mes énumérations dans les classes.

Comme l'a signalé Richard Corden, l'avantage d'une classe est qu'il s'agit d'un type au sens c ++ et que vous pouvez donc l'utiliser avec des modèles.

J'ai une classe spéciale toolbox :: Enum pour mes besoins que je spécialise pour tous les modèles qui fournissent des fonctions de base (principalement: mapper une valeur enum à une std :: string afin que les E / S soient plus faciles à lire).

Mon petit modèle a également l'avantage supplémentaire de vraiment vérifier les valeurs autorisées. Le compilateur est un peu laxiste pour 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<boolean>(1);
  return (x == False || x == True) ? 0 : 1;
} // main

Cela m'a toujours dérangé que le compilateur n'attrape pas cela, car il vous reste 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<example>(3);
  return (y == Zero || y == One || y == Two) ? 0 : 1;
} // main

Une fois de plus, main renverra une erreur.

Le problème est que le compilateur ajustera l'énumération dans la plus petite représentation disponible (ici nous avons besoin de 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érez avoir une boucle sur les valeurs possibles au lieu d'un commutateur afin de ne pas avoir à modifier tous vos commutateurs chaque fois que vous ajoutez une valeur à l'énumération.

Dans l'ensemble, mon petit assistant facilite vraiment les choses pour mes énumérations (bien sûr, cela ajoute des frais généraux) et ce n'est possible que parce que j'imbrique chaque énumération dans sa propre structure :)

Matthieu M.
la source
4
Intéressant. Voulez-vous partager la définition de votre classe Enum?
momeara