2009-08-19 18 views
7

Penso che il problema sia piuttosto comune. Hai una stringa di input e devi chiamare una funzione a seconda del contenuto della stringa. Qualcosa come un interruttore() per le stringhe. Pensa alle opzioni della riga di comando.Alla ricerca del più elegante dispatcher di codici

Attualmente sto usando:

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) { 
... 
} 

e nell'intestazione

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

Così ogni foo e bar devo ripetere quattro (4!) Volte. So che posso alimentare i puntatori e le stringhe di funzione a un array statico prima e fare il dispatching in un ciclo, salvando alcuni se ... altre linee. Ma c'è qualche trucco macro (o abuso del preprocessore, a seconda del POV), che rende possibile definire in qualche modo la funzione e allo stesso tempo aggiornare automaticamente l'array? Quindi dovrei scriverlo solo due volte, o forse una volta se usato in linea?

Sto cercando una soluzione in C o C++.

+4

Questa domanda è stata posta più volte e mi ha risposto qui http://stackoverflow.com/questions/659581/replace-giant-switch-statement-with-what –

+2

Chiede anche uno schema di registrazione di qualche tipo a ridurre lo sforzo nel mantenere il dizionario. – djna

+0

Lo è, ma non credo che ce ne sia uno che lo soddisfi. Potresti fare qualcosa di grezzo e di brutto con i macro, ma è più un problema che ne vale la pena. –

risposta

1

La soluzione macro brutto, che tipo di-chiesto. Si noti che non si registra automaticamente, ma mantiene alcune cose sincronizzate, e inoltre causerà errori di compilazione se si aggiunge solo ai mapping e non alla funzione nel file sorgente.

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

Beh, scontato, è brutto, ma sembra tagliare le ripetizioni a due. Ma un #include nel mezzo del codice è davvero brutto. +1 – hirschhornsalz

+1

+1, per lo stesso motivo. Inoltre, assumendo che ogni comando "pippo" risulti nella chiamata a cmd_foo, con un po 'di stringificazione si evita anche la ripetizione nei parametri. –

+0

Sì, sto pensando lungo le linee dell'operatore "incolla" a. – hirschhornsalz

8

Sembra che tu stai cercando il Command pattern

Qualcosa di simile a questo:

creare una mappa di come questo

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

poi basta usare la chiave per eseguire il comando in questo modo. ...

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

Per registrare i comandi basta fare questo

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

No. Probabilmente è fuorviante che ho usato funzioni chiamate "cmd_ *" ma potrebbero essere qualcosa di diverso, la necessità di non rappresentare comandi. Inoltre il dispatcher non ha bisogno di essere legato a un oggetto. – hirschhornsalz

+2

@drhirsch. Non devono essere comandi effettivi. Questo è solo il nome del modello. Fondamentalmente è un modo di eseguire un codice predefinito basato su un input – Glen

+0

. Penso che la soluzione che Neil e Glen stanno suggerendo sia la risposta giusta. Non include l'auto-registrazione, ma non sono sicuro che ci sia un modo pulito per farlo in C++. In Java o C#, sarebbe molto fattibile, però. –

2

as alternative to the Command pattern si può costruire una tabella hash di stringa - puntatori> funzione :

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

Questa non è un'alternativa reale a _pattern_: è lo stesso concetto, diversamente implementato. – xtofl

5

La soluzione di base, per il mio link nel commento questione, è mappare una stringa a una chiamata di funzione di qualche tipo.

Per registrare in realtà la stringa -> Funzione coppia puntatore/funtore:

In primo luogo, hanno un Singleton oggetto dispatcher (shock orrore!!). Chiamiamolo TheDispatcher - è un wrapper per un map<string,Func>, dove Func è il puntatore di funzione o il tipo di functor.

Poi, hanno una classe di registro:

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

Ora nelle vostre singole unità di compilazione si crea oggetti statici (scossa di orrore!):

Register r1_("hello", DoSayHello); 

Questi oggetti verranno creati (supponendo il codice non è in una libreria statica) e si registrerà automaticamente con TheDispatcher.

E in fase di esecuzione, cercare le stringhe in TheDispatcher ed eseguire la relativa funzione/funtore.

1

Dovrai almeno definire le funzioni e aggiungerle ad un registro. (Se devono essere funzioni di membri non inline di qualche classe, dovrai anche dichiararli.) Oltre a qualche lingua specifica del dominio che genera il codice reale (come cjhuitt's macro hackery), non vedo alcun modo per menzionare queste funzioni due (o tre) volte.

+0

Sì, penso di averlo finalmente inserito nel mio grosso cranio. – hirschhornsalz

Problemi correlati