web-dev-qa-db-fra.com

espaces de noms pour les types d'énumération - meilleures pratiques

Souvent, on a besoin de plusieurs types énumérés ensemble. Parfois, on a un conflit de noms. Deux solutions à cela viennent à l'esprit: utilisez un espace de noms, ou utilisez des noms d'élément enum "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: //...
        //...
    }
  }
96
xtofl

Réponse originale C++ 03:

L'avantage d'un namespace (sur un class) est que vous pouvez utiliser les déclarations using quand tu veux.

Le problème avec l'utilisation d'un namespace est 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 qu'elles s'appellent eFeelings

Pour un code plus simple, j'utilise un 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 de l'examiner plus en détail.

Plus récent, conseils C++ 11:

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

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

66
Drew Dormann

Pour info en C++ 0x il y a une nouvelle syntaxe pour des cas comme ceux que vous avez mentionnés (voir page wiki C++ 0x )

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

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

J'ai un gros fichier d'en-tête qui contient toutes mes énumérations de projet, car ces énumérations sont partagées entre les classes de travailleurs et il n'est pas logique de placer les énumérations dans les classes de travailleurs elles-mêmes.

Le struct évite le public: le sucre syntaxique, et le typedef vous permet de déclarer des 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 avez pour utiliser le nom du type enum lorsque vous référez 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: ... ;
    }
}
11
Mark Lakata

J'éviterais certainement d'utiliser une classe pour cela; utilisez plutôt un espace de noms. La question se résume à utiliser un espace de noms ou à utiliser des identifiants uniques pour les valeurs d'énumération. Personnellement, j'utiliserais un espace de noms afin que mes identifiants soient plus courts et, je l'espère, plus explicites. Le code d'application pourrait alors 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: //...
        //...
    }
}
9
Charles Anderson

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

Un avantage possible pour l'utilisation d'une classe est qu'ils peuvent être utilisés comme arguments de type de 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 en utilisant, 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 prendrez dans votre projet!

7
Richard Corden

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) à C++ d'autoriser un cast à partir d'une valeur non valide,
  2. définir une valeur par défaut (non nulle) 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 passer le type à une instruction switch.

7
Michał Górny

Étant donné que les énumérations sont limitées à 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 namespace se sent comme un sac de maintien, tandis que class se sent comme un objet robuste (cf. le struct vs class débat). Un avantage possible d'un espace de noms est qu'il peut être étendu ultérieurement - utile si vous traitez avec du code tiers que vous ne pouvez pas modifier.

Bien entendu, tout cela est théorique lorsque nous obtenons des classes enum avec C++ 0x.

5
Michael Kristofik

J'ai également tendance à envelopper mes énumérations dans les cours.

Comme indiqué par 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 classe spéciale toolbox :: Enum pour mes besoins que je spécialise pour chaque modèle qui fournit des fonctions de base (principalement: mappage d'une valeur enum à une chaîne std :: 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 d'énumération qui n'a aucun sens (et que 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 retournera une erreur.

Le problème est que le compilateur adaptera l'énumération dans la plus petite représentation disponible (ici nous avons besoin de 2 bits) et que tout ce qui tient 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 que vous n'ayez pas à 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, il ajoute des frais généraux) et ce n'est possible que parce que j'imbrique chaque énumération dans sa propre structure :)

3
Matthieu M.