2010-04-16 17 views
11

Supposons que j'ai une liste de #define s dans un fichier d'en-tête pour une bibliothèque externe. Ces #define représentent les codes d'erreur renvoyés par les fonctions. Je veux écrire une fonction de conversion qui peut prendre en entrée un code d'erreur et retourner en sortie un littéral de chaîne représentant le nom réel #define.Possibilité de convertir la liste de #defines en chaînes

À titre d'exemple, si je

#define NO_ERROR 0 
#define ONE_KIND_OF_ERROR 1 
#define ANOTHER_KIND_OF_ERROR 2 

Je voudrais une fonction de pouvoir appelé comme

int errorCode = doSomeLibraryFunction(); 
if (errorCode) 
    writeToLog(convertToString(errorCode)); 

et ont convertToString() être en mesure de l'auto-convertir ce code d'erreur sans un boîtier de commutateur géant ressemblant à

const char* convertToString(int errorCode) 
{ 
    switch (errorCode) 
    { 
     case NO_ERROR: 
      return "NO_ERROR"; 
     case ONE_KIND_OF_ERROR: 
      return "ONE_KIND_OF_ERROR"; 
     ... 
    ... 
... 

J'ai le sentiment que si t Son possible, il serait possible d'utiliser des modèles et métaprogrammation, mais cela ne fonctionnerait que les codes d'erreur étaient en fait un type et non pas un tas de macros de processeur.

+0

Désolé, je devrais faire un addendum, après avoir vu certaines des réponses: Ce que j'aimerais pouvoir faire, c'est générer au moment de la compilation une liste de ces chaînes et d'y indexer. – brandonC

+0

dupe de: http://stackoverflow.com/questions/2571816/is-it-possible-to-define-enumalpha –

Répondre

19

je le fais normalement le chemin géant de cas de commutation, même si je fais un peu plus facile avec:

#define STR(code) case code: return #code 
switch (errorCode) 
{ 
    STR(NO_ERROR); 
    STR(ONE_KIND_OF_ERROR); 
} 

Ceci est une bonne question, je suis curieux de voir ce que de meilleures façons dont les gens ont

+0

+1 Je n'ai jamais pensé à cette méthode auparavant. –

+0

Je suggère d'appeler de telles macros CASE au lieu de STR (ou quoi que ce soit réellement fait). – hlovdal

+0

+1 pour utiliser l'opérateur de stringification '#'. –

4

Vous avez raison. Il n'y a aucun moyen de récupérer les identificateurs définis par le préprocesseur lors de l'exécution (sauf si vous pouvez lire la source, mais c'est tricher). Vous feriez mieux de créer un tableau constant des noms et de l'indexer avec le code d'erreur (avec bien sûr les vérifications des limites) - il devrait être assez facile d'écrire un script pour le générer.

+0

Cause du downvote? –

+0

Mais est-il possible que le tableau constant puisse être généré au moment de la compilation sans passer à une étape de pré-construction personnalisée? – brandonC

+0

Il n'y a aucun moyen de le générer au moment de la compilation en utilisant le standard C/C++. Le plus proche que vous pouvez obtenir est quelque chose comme la réponse 'STR (...)' de Michael Morzek. –

0

#define FOO 1 est géré par le préprocesseur comme un simple remplacement de texte. Si vous voulez conserver ces définitions, vous avez besoin du commutateur géant.

6

Une autre façon de le faire qui est populaire dans le code généré est:

#define NO_ERROR 0 
#define ONE_KIND_OF_ERROR 1 
#define ANOTHER_KIND_OF_ERROR 2 
static const char* const error_names[] = {"NO_ERROR", "ONE_KIND_OF_ERROR", "ANOTHER_KIND_OF_ERROR"}; 

const char* convertToString(int errorCode) {return error_names[errorCode];} 

Je préfère la voie de cas interrupteur I already mentioned, mais en fonction de la façon dont votre code est structuré, il pourrait être plus facile dans le cadre de votre processus de construction pour générer ce tableau automatiquement

+0

C'est certainement un moyen cool, et beaucoup plus facile sur le typage que l'interrupteur. Je souhaite juste qu'il y avait un moyen de générer le tableau au moment de la compilation. – brandonC

2

jetez un coup d'œil au préprocesseur boost. Vous pouvez créer une liste/série/séquence de paires de code:

#define codes ((1,"code1"))((...)) 

#define code1 1 
... 

// then use preprocessor FOR_EACH to generate error handlers 

lien utile:

http://www.boost.org/doc/libs/1_41_0/libs/preprocessor/doc/ref/seq_for_each.html

http://www.boost.org/doc/libs/1_41_0/libs/preprocessor/doc/ref/tuple_elem.html

+2

Voici un exemple complet d'un type de problème similaire implémenté avec le préprocesseur Boost: http://stackoverflow.com/questions/2576868/cc-enums-detect-when-multiple-items-map-to-same-value/2577102# 2577102 (il ne prendrait probablement pas beaucoup de travail pour modifier cela)

+0

@James merci, j'étais trop paresseux pour fournir une liste plus complète. – Anycorn

+0

C'est une solution vraiment cool, et certainement quelque chose de proche de ce que je cherchais. Je ne pense pas que cela fonctionnerait dans ma situation particulière, ne contrôlant pas le fichier .h et tout, mais certainement quelque chose que je serais ravi de trouver une utilisation pour – brandonC

1

Une possibilité est ici pour écrire un petit programme qui analyse le. Fichier h qui contient les #defines et qui émet le code source correspondant à la fonction convertToString(). Vous pouvez ensuite exécuter automatiquement ce programme dans le cadre de votre processus de génération à chaque modification du fichier .h. C'est un peu plus de travail à l'avance, mais une fois qu'il est implémenté, vous n'aurez plus jamais besoin de mettre à jour manuellement votre fonction de conversion de chaîne int < ->.

Ceci est particulièrement utile si vous souhaitez prendre en charge du code dans plusieurs langues utilisant les mêmes constantes. Par exemple, dans une de mes applications, la modification de mon fichier d'en-tête #defines entraîne la régénération automatique des fichiers C++, Python et Java correspondants. Cela rend la maintenance du projet beaucoup plus facile et moins sujette aux erreurs.

+0

Je suis d'accord avec ça. Mon espoir, cependant, était de pouvoir faire cette génération entièrement dans le préprocesseur C/C++. Ma plus grande réserve avec l'écriture d'un outil de pré-construction personnalisé pour la génération de code est que je passerais plus de temps à écrire et à maintenir l'outil de génération de code que d'utilisation, d'autant plus que les # define sont fournis partie d'une bibliothèque externe, j'espère qu'ils seraient très peu susceptibles de changer. – brandonC

1

Si vous voulez vraiment garder le #define, j'irais avec l'élégante réponse #define STR(code) de Michael. Mais les définitions sont plus C que C++, et le gros inconvénient des définitions est que vous ne pouvez pas les mettre dans un espace de noms. Ils polluent l'espace de noms global de tout programme que vous les inclure dans S'il est en votre pouvoir de le changer, je recommande l'utilisation d'un énum anonyme à la place.

enum{ NO_ERROR ONE_KIND_OF_ERROR ANOTHER_KIND_OF_ERROR } 

C'est exactement la même chose que les #define s que vous avez , et vous pouvez le mettre dans un espace de noms. Et maintenant, vous pouvez utiliser l'autre réponse de Michael impliquant le tableau static const char* const error_names pour faire ce que vous aviez demandé à l'origine.

+1

C'est une bonne réponse, et certainement ce que je ferais si je contrôlais réellement les fichiers d'en-tête, mais malheureusement, ils sont fournis dans une bibliothèque externe, donc changer les #defines en enums, bien que préférable, n'est pas vraiment une option dans ce cas :( – brandonC

+0

@Goose, je l'ai fait de cette façon tout le long.Un nouveau point de vue avec le commutateur .. – rocknroll

+0

@brandonC: Je pensais que c'était le cas.Oh bien –

1

Vous pouvez réellement l'avoir dans les deux sens, c'est-à-dire avoir deux fonctions qui traduisent du code à l'erreur d'avant en arrière. La première chose est bien sûr que #define ne devrait pas être utilisé pour les constantes, une énumération serait probablement la meilleure, mais une énumération ne peut pas être étendue, ce qui nécessite que toutes vos erreurs soient définies au même endroit (ouch, merci beaucoup pour les dépendances ...)

Vous pouvez le faire d'une autre manière, en utilisant des espaces de noms pour isoler les symboles, et un prétraitement pour gérer toute la génération. Pour la partie de recherche, nous utiliserons un Bimap.

nous devons d'abord définir une classe de gestionnaire (pas nécessaire inline tout cela, mais il est plus facile pour des exemples)

#include <boost/bimap.hpp> 
#include <boost/optional.hpp> 

namespace error 
{ 

    class Handler 
    { 
    public: 
    typedef boost::optional<int> return_code; 
    typedef boost::optional<std::string> return_description; 

    static bool Register(int code, const char* description) 
    { 
     typedef error_map::value_type value_type; 
     bool result = MMap().insert(value_type(code,description)).second; 

     // assert(result && description) 
     return result; 
    } 

    static return_code GetCode(std::string const& desc) 
    { 
     error_map::map_by<description>::const_iterator it 
      = MMap().by<description>().find(desc); 
     if (it != MMap().by<description>().end()) return it->second; 
     else return return_code(); 
    } 

    static return_description GetDescription(int c) 
    { 
     error_map::map_by<code>::const_iterator it 
      = MMap().by<code>().find(c); 
     if (it != MMap().by<code>().end()) return it->second; 
     else return return_description(); 
    } 

    typedef std::vector< std::pair<int,std::string> > errors_t; 
    static errors_t GetAll() 
    { 
     errors_t result; 
     std::for_each(MMap().left.begin(), MMap().left.end(), 
        result.push_back(boost::lambda::_1)); 
     return result; 
    } 

    private: 
    struct code {}; 
    struct description {}; 

    typedef boost::bimap< 
     boost::tagged<int, code>, 
     boost::tagged<std::string, description> 
    > error_map; 

    static error_map& Map() { static error_map MMap; return MMap; } 
    }; 

    // Short-Hand 
    boost::optional<int> GetCode(std::string const& d) 
    { 
    return Handler::GetCode(d); 
    } 

    boost::optional<std::string> GetDescription(int c) 
    { 
    return Handler::GetDescription(c); 
    } 
} // namespace error 

Ensuite, nous avons juste besoin de fournir un peu de sucre syntaxique:

#define DEFINE_NEW_ERROR(Code_, Description_)   \ 
    const int Description_ = Code_;      \ 
    namespace error {          \ 
    const bool Description##_Registered =    \ 
     ::error::Handler::Register(Code_, #Description_); \ 
    } 

Nous pourrions être un peu plus violents en cas d'enregistrement d'une erreur inconnue (affirmer par exemple).

Et puis nous pouvons toujours envelopper cette macro dans une macro qui peut définir plusieurs symboles en une seule fois ... mais cela reste à titre d'exercice.

Utilisation:

// someErrors.hpp 
#include "error/handler.hpp" 

DEFINE_NEW_ERROR(1, AnError) 
DEFINE_NEW_ERROR(2, AnotherError) 

// someFile.cpp 
#include <iostream> 
#include "error/handler.hpp" 

int main(int argc, char* argv[]) 
{ 
    int code = 6; 
    boost::optional<std::string> desc = error::GetDescription(code); 

    if (desc) 
    { 
    std::cout << "Code " << code << " is mapped to <" << *desc << ">" << std::endl; 
    } 
    else 
    { 
    std::cout << "Code " << code << " is unknown, here is the list:\n"; 

    ::error::Handler::errors_t errors = ::Error::Handler::GetAll(); 

    std::for_each(errors.begin(), errors.end(), std::cout << " " << _1); 

    std::cout << std::endl; 
    } 
} 

Disclaimer: Je ne suis pas trop sûr de la syntaxe lambda, mais il ne simplifié l'écriture.

+0

Encore une fois, une solution géniale, si je J'écrivais une bibliothèque et contrôlais les codes d'erreur qui sortaient Malheureusement, quelqu'un d'autre m'a déjà fourni la bibliothèque qui choisit de renvoyer les codes d'erreur via # define'd ints Eh bien, j'ai pensé que ce que je voulais n'était probablement pas possible. pour la réponse informative, cependant. – brandonC