2010-09-17 3 views
4

J'écris le portage du fichier-io ensemble de fonctions de c dans une classe C++. Les "nombres magiques" (constantes sans nom) abondent.Devrais-je utiliser un enum ou plusieurs const pour les constantes non-séquentielles en C++?

Les fonctions lisent un en-tête de fichier comportant un certain nombre d'entrées spécifiques dont les emplacements sont actuellement indiqués par des nombres magiques.

Un programmeur vétéran m'a enseigné il y a deux ans que l'utilisation de «nombres magiques» est intrinsèquement mauvaise, et j'ai donc essayé d'éviter d'utiliser des constantes sans nom dans mon port. Donc, je veux créer une sorte de liste de constantes d'où les entrées sont stockées. Jusqu'à présent, j'ai trouvé deux solutions qui semblent relativement sûres - utiliser un ensemble de constantes entouré d'un espace de noms ou une énumération en espace de noms. Puis-je utiliser l'une ou l'autre des solutions en toute sécurité? Y aurait-il des avantages à l'un par rapport à l'autre?

par exemple.
OPTION 1

namespace hdr_pos { 
    const unsigned int item_1_pos=4; 
    const unsigned int item_2_pos=8; 
    const unsigned int item_3_pos=12; 
    const unsigned int item_4_pos=24; 
    const unsigned int item_5_pos=32; 
}; 

OPTION 2

namespace hdr_pos { 
    enum e { 
     item_1_pos=4, 
     item_2_pos=8, 
     item_3_pos=12, 
     item_4_pos=24, 
     item_5_pos=32 
    }; 
}; 

Y at-il de toute façon d'éviter les doublons, pour attraper si je change les positions en raison d'une future mise à jour l'en-tête de fichier, mais oublier changer l'un d'eux?

Veuillez garder ceci factuel et non-subjectif. S'il n'y a aucun avantage que vous connaissez, n'hésitez pas à répondre à cette question.

Note: J'utiliserais plus de noms descriptifs, bien sûr, dans mon implémentation actuelle; Je viens d'appeler les choses item_ < #> _... pour l'amour des exemples ...

+0

Vous pouvez obtenir le compilateur pour vous assurer qu'il n'y a pas de valeurs d'énumérateur en double, mais c'est un battement et c'est un peu compliqué. [Vous pouvez voir ma réponse à cette autre question pour savoir comment faire] (http://stackoverflow.com/questions/2576868/cc-enums-detect-when-multiple-items-map-to-same-value/2577102 # 2577102). –

+0

@James Wow c'est désordonné ... Je ne sais pas si ça en vaut la peine ici. Je veux dire que si vous vous trompez, le fichier ne sera pas analysé correctement, et vous le saurez assez rapidement, je suppose, après la compilation ... Merci, cependant, cela me donne un aperçu de ma dernière question là-bas. –

+0

C'est compliqué si c'est la seule chose que vous utilisez pour générer la macro.J'ai écrit un ensemble de macros pour générer des fonctions ToString/ToEnum, des fonctions de vérification de plage et des opérateurs d'insertion et d'extraction de flux pour les énumérations; Si vous utilisez beaucoup d'énumérations, une macro comme celle-ci peut être très, très utile. –

Répondre

2

Je peux voir deux avantages à l'utilisation d'une énumération. Tout d'abord, certains débogueurs peuvent convertir les constantes en noms de variables enum (ce qui peut faciliter le débogage dans certains cas). En outre, vous pouvez déclarer une variable d'un type énuméré qui ne peut contenir qu'une valeur de cette énumération. Cela peut vous donner une forme supplémentaire de vérification de type que vous n'auriez pas simplement en utilisant des constantes.

La vérification pour voir si une valeur est dupliquée peut dépendre de votre compilateur particulier. La façon la plus simple de le faire serait probablement d'écrire un script externe qui analysera votre définition d'énumération et signalera si une valeur est dupliquée ou non (vous pouvez l'exécuter dans le cadre de votre processus de construction si vous le souhaitez).

+0

Un bon conseil sur les deux points. Je n'y ai même pas pensé, mais je vais probablement passer un index à certaines de mes fonctions, donc utiliser une énumération semble sage pour les raisons que vous avez mentionnées ... –

+0

Une chose que j'ai oublié de mentionner: Si à tout moment vous peut-être besoin de prendre l'adresse d'une de ces valeurs, vous voudrez utiliser une constante. Vous ne pouvez pas faire un pointeur vers une valeur énumérée, sauf si vous affectez la valeur à une variable temporaire et prenez l'adresse de celle-ci. – bta

0

Le titre de votre question suggère que la principale raison pour laquelle vous avez des doutes sur l'utilisation d'un ENUM est que vos constantes sont non itérative. Mais en C++, les types enum sont déjà non-itératifs. Vous devez sauter à travers un certain nombre de cerceaux pour faire un type enum itératif.

Je dirais que si vos constantes sont liées par nature, alors enum est une très bonne idée, que les constantes soient itératives ou non. Le principal inconvénient des énumérations est l'absence totale de contrôle de type. Dans de nombreux cas, vous préférerez peut-être avoir un contrôle strict sur les types de vos valeurs constantes (comme les avoir non signées) et c'est quelque chose qui ne peut pas vous aider avec (au moins pour le moment).

+0

Je change le titre en séquentiel. Je veux dire que généralement, je vois des exemples enum utilisés pour une suite de nombres séquentielle par rapport à une chaîne croissante discontinue de #s comme le mien ici ... –

0

Une chose à garder à l'esprit est que vous ne pouvez pas prendre l'adresse d'un enum:

const unsigned* my_arbitrary_item = &item_1_pos;

0

Si elles sont purement constantes et ne nécessitent aucune substance d'exécution (comme ne peut pas init enum avec une valeur non-enum) alors ils devraient juste être ints const non signés. Bien sûr, l'énumération est moins typée, mais c'est à côté de ça.

1

J'ai déjà traité cette situation pour des codes d'erreur.

J'ai vu des gens en utilisant les énumérations des codes d'erreur, et cela pose quelques problèmes:

  1. vous pouvez attribuer un entier à l'ENUM qui ne correspond pas à aucune valeur (trop mauvais)
  2. la valeur elle-même est déclarée dans un entête, ce qui signifie que la réaffectation du code d'erreur (cela arrive ...) casse la compatibilité du code, vous devez également faire attention lors de l'ajout d'éléments ...
  3. vous devez définir tous les codes dans le même en-tête , même si souvent un peu de code est naturellement limité à une petite partie de l'application, car les enums ne peuvent pas être "étendus"
  4. il n'y a pas de contrôle qu'un même code n'est pas attribué deux fois
  5. vous ne pouvez pas itérer sur les différents domaines d'un enum

Lors de la conception ma solution de codes d'erreur, j'ai donc choisi une autre route: constantes dans un espace de noms, définis dans les fichiers sources, dont les points d'adresse 2 et 3. Pour gagner en sécurité de type cependant, les constantes ne sont pas int, mais une classe spécifique Code:

namespace error { class Code; } 

Ensuite, je peux définir plusieurs Erro fichiers r:

// error/common.hpp 

namespace error 
{ 
    extern Code const Unknown; 
    extern Code const LostDatabaseConnection; 
    extern Code const LostNASConnection; 
} 

// error/service1.hpp 
// error/service2.hpp 

Je ne pas résolu le problème de distribution arbitraire si (constructeur est explicite, mais public), parce que dans mon cas, on m'a demandé de transmettre les codes d'erreur renvoyés par d'autres serveurs, et je doute n » Je voulais les connaître tous (cela aurait été trop fragile)

Cependant, j'ai réfléchi à cela, en rendant le constructeur requis privé et en renforçant l'utilisation d'un constructeur, nous allons même en avoir 4. et 5. en un coup:

// error/code.hpp 

namespace error 
{ 
    class Code; 

    template <size_t constant> Code const& Make(); // not defined here 

    class Code: boost::totally_ordered<Code> 
    { 
    public: 
    Code(): m(0) {} // Default Construction is useful, 0 is therefore invalid 

    bool operator<(Code const& rhs) const { return m < rhs.m; } 
    bool operator==(Code const& rhs) const { return m == rhs.m; } 

    private: 
    template <size_t> friend Code const& Make(); 
    explicit Code(size_t c): m(c) { assert(c && "Code - 0 means invalid"); } 

    size_t m; 
    }; 

    std::set<Code> const& Codes(); 
} 


// error/privateheader.hpp (inaccessible to clients) 

namespace error 
{ 
    std::set<Code>& PrivateCodes() { static std::set<Code> Set; return Set; } 

    std::set<Code> const& Codes() { return PrivateCodes(); } 

    template <size_t constant> 
    Code const& Make() 
    { 
    static std::pair< std::set<Code>::iterator, bool > r 
     = PrivateCodes().insert(Code(constant)); 
    assert(r.second && "Make - same code redeclared"); 
    return *(r.first); 
    } 
} 

// 
// We use a macro trick to create a function whose name depends 
// on the code therefore, if the same value is assigned twice, the 
// linker should complain about two functions having the same name 
// at the condition that both are located into the same namespace 
// 
#define MAKE_NEW_ERROR_CODE(name, value)   \ 
    Make<value>(); void _make_new_code_##value(); 


// error/common.cpp 

#include "error/common.hpp" 

#include "privateheader.hpp" 

namespace error 
{ 
    Code const Unkown = MAKE_NEW_ERROR_CODE(1) 
    /// .... 
} 

Un peu plus de travail (pour e framework), et uniquement la vérification du temps de liaison/d'exécution de la même vérification d'affectation. Bien qu'il soit facile de diagnostiquer les doublons simplement en scannant le motif MAKE_NEW_ERROR_CODE

Amusez-vous!

Questions connexes