2009-08-19 11 views
7

Je pense que le problème est assez commun. Vous avez une chaîne d'entrée et devez appeler une fonction en fonction du contenu de la chaîne. Quelque chose comme un commutateur() pour les chaînes. Pensez aux options de la ligne de commande.Vous cherchez le répartiteur de code le plus élégant

Actuellement, je suis en utilisant:

using std::string; 

void Myclass::dispatch(string cmd, string args) { 
    if (cmd == "foo") 
     cmd_foo(args); 
    else if (cmd == "bar") 
     cmd_bar(args); 
    else if ... 
     ... 
    else 
     cmd_default(args); 
} 

void Myclass::cmd_foo(string args) { 
... 
} 

void Myclass::cmd_bar(string args) { 
... 
} 

et dans l'en-tête

class Myclass { 
    void cmd_bar(string args); 
    void cmd_foo(string args); 
} 

Ainsi, chaque foo et bar, je dois répéter quatre (4!) Fois. Je sais que je peux alimenter les pointeurs et les chaînes de fonction en un tableau statique avant de faire le dispatching en boucle, en sauvegardant des lignes if ... else. Mais y a-t-il de la supercherie (ou abus de préprocesseur, selon le POV), qui permet de définir la fonction et de la mettre à jour automagiquement? Donc, je devrais l'écrire seulement deux fois, ou peut-être une fois si utilisé en ligne?

Je cherche une solution en C ou C++.

+4

Cette question a été posée à plusieurs reprises, et j'ai répondu par moi ici http://stackoverflow.com/questions/659581/replace-giant-switch-statement-with-what –

+2

Il demande également un plan d'enregistrement de quelque sorte à réduire l'effort dans la maintenance du dictionnaire. – djna

+0

Il l'est, mais je ne pense pas qu'il y en ait un qui le satisferait. Vous pourriez faire quelque chose de grossier et laid avec des macros, mais c'est plus de problèmes que ça en vaut la peine. –

Répondre

1

La solution macro laide, que vous-genre de demandé. Notez qu'il ne s'enregistre pas automatiquement, mais il garde certaines choses synchronisées, et provoquera aussi des erreurs de compilation si vous ajoutez seulement aux mappages, et non la fonction dans le fichier source.

Mappings.h:

// Note: no fileguard 
// The first is the text string of the command, 
// the second is the function to be called, 
// the third is the description. 
UGLY_SUCKER("foo", cmd_foo, "Utilize foo."); 
UGLY_SUCKER("bar", cmd_bar, "Turn on bar."); 

parser.h:

class Myclass { 
... 
protected: 
    // The command functions 
    #define UGLY_SUCKER(a, b, c) void b(args) 
    #include Mappings.h 
    #undef UGLY_SUCKER 
}; 

Parser.cpp:

void Myclass::dispatch(string cmd, string args) { 
    if (cmd == "") 
     // handle empty case 
#define UGLY_SUCKER(a, b, c) else if (cmd == a) b(args) 
#include Mappings.h 
#undef UGLY_SUCKER 
    else 
     cmd_default(args); 
} 

void Myclass::printOptions() { 
#define UGLY_SUCKER(a, b, c) std::cout << a << \t << c << std::endl 
#include Mappings.h 
#undef UGLY_SUCKER 
} 

void Myclass::cmd_foo(string args) { 
... 
} 
+0

Eh bien, accordé, c'est moche, mais il semble couper les répétitions à deux. Mais un #include au milieu du code est vraiment moche. +1 – hirschhornsalz

+1

+1, pour la même raison. En outre, en supposant que chaque commande "foo" entraîne l'appel de cmd_foo, avec un peu de stringification, vous pouvez également éviter la répétition dans les paramètres. –

+0

Oui, je pense à l'opérateur "pâte". – hirschhornsalz

8

On dirait que vous êtes à la recherche de la Command pattern

Quelque chose comme ceci:

Créer une carte comme cette

std::map<std::string, Command*> myMap; 

puis il suffit d'utiliser votre clé pour exécuter la commande comme celui-ci. ...

std::map<std::string, Command*>::iterator it = myMap.find(str); 
if(it != myMap.end()) { 
    it->second->execute() 
} 

Pour enregistrer vos commandes, il vous suffit de faire ce

myMap["foo"] = new CommandFoo("someArgument"); 
myMap["bar"] = new CommandBar("anotherArgument"); 
+0

Non. Il est probablement trompeur d'avoir utilisé des fonctions appelées "cmd_ *" mais elles pourraient être quelque chose de différent, le besoin de ne pas représenter les commandes. De plus, le répartiteur n'a pas besoin d'être lié dans un objet. – hirschhornsalz

+2

@drhirsch. Ils ne doivent pas être des commandes réelles. C'est juste le nom du motif. Fondamentalement, c'est une façon d'exécuter un code prédéfini basé sur une entrée – Glen

+0

Je pense que la solution proposée par Neil et Glen est la bonne réponse. Cela n'inclut pas l'auto-enregistrement, mais je ne suis pas sûr qu'il existe une façon propre de le faire en C++. En Java ou C#, ce serait très faisable, cependant. –

2

as alternative to the Command pattern vous pouvez construire une Hashtable de chaîne ->pointeurs de fonction:

typedef void (*cmd)(string); 
+1

Ceci n'est pas une alternative réelle au _pattern_: c'est le même concept, implémenté différemment. – xtofl

5

La solution de base, par mon lien dans le commentaire de question, est pour mapper une chaîne à un appel de fonction quelconque.

Pour vous inscrire en fait la chaîne -> pointeur fonction/paire foncteur:

Tout d'abord, ont un singleton objet répartiteur (choc horreur!). Appelons-le TheDispatcher - c'est un wrapper pour un map<string,Func>, où Func est le pointeur de fonction ou le type de foncteur.

Ensuite, ont une classe de registre:

struct Register { 
    Register(comst string & s, Func f) { 
     TheDispatcher.Add(s, f); 
    } 
}; 

maintenant dans vos unités de compilation individuelles que vous créez objets statiques (choc d'horreur!):

Register r1_("hello", DoSayHello); 

Ces objets seront créés (en supposant le code n'est pas dans une bibliothèque statique) et s'inscrira automatiquement avec TheDispatcher.

Et lors de l'exécution, vous recherchez des chaînes dans TheDispatcher et exécutez la fonction/foncteur associé.

1

Vous devrez au moins définir les fonctions et les ajouter à un registre. (Si elles doivent être des fonctions membres non-en-ligne d'une classe, vous devrez également les déclarer.) À part un certain langage spécifique au domaine qui génère le code réel (comme cjhuitt's macro hackery), je ne vois aucun moyen de mentionner ces deux fonctions (ou trois) fois.

+0

Oui, je pense que je l'ai enfin dans mon crâne épais. – hirschhornsalz

Questions connexes