2010-11-20 14 views
9

Ho bisogno di aiuto con C++, per favore!C++ Puntatori di funzione con numero sconosciuto di argomenti

Sto scrivendo un parser di comandi per un piccolo gioco di testo, e ho incontrato alcuni problemi. Il parser dovrebbe leggere e analizzare i comandi inseriti dal lettore.

La soluzione più ovvia e semplice per questo potrebbe essere qualcosa di simile (scritto in pseudo-codice):

command <- read input from the player 
if command == COMMAND1 
    do command1 
else if command == COMMAND 2 
    do command2 
... 

sto scrivendo in C++, così mi è stato pensare che potrei risolvere questo problema utilizzando un mappa associativa e puntatori di funzioni. Non sono così familiare nell'usare i puntatori di funzioni, quindi potrebbe essere il motivo per cui ho dei problemi. Quello che voglio fare è avere una sorta di loop che aspetta input, analizzare l'input che viene inserito e chiama una funzione a seconda del comando dato. Ecco qualche C++ - ish pseudo-codice che descrive quello che penso:

while(1) { 
cin >> input; 
char * tok = strtok(input, " ") 
functionpointer fptr = command_map.find(tok); 
... // here, I get stuck on what to do.. 
} 

quindi spero mi faccio un po 'chiare su quello che voglio che accada. Il giocatore avrebbe potuto avere qualcosa di ingresso come

> go south 

e ho potuto finire il codice con qualcosa di simile:

destination = strtok(NULL, " "); 
fptr(destination); 

In sostanza, il valore restituito dalla mappa sarebbe la funzione che esegue il comando " vai ", e apparentemente questa funzione prende un argomento, la destinazione. Di nuovo, questo è un codice pseudo-ish C++. Quindi ho ottenuto il comando "go" coperto. Ma ora dico che voglio avere il comando follwing:

> attack troll with sword 

Ora mi sento che ho bisogno di fare qualcosa di simile:

while(1) { 
cin >> input; 
char * tok = strtok(input, " ") 
functionpointer fptr = command_map.find(tok); 
if(tok == "go"){ 
    destination = strtok(NULL, " "); 
    fptr(destination); 
} else if (tok == "attack") { 
    target = strtok(NULL, " "); 
    weapon = strtok(NULL, " "); 
    fptr(target, weapon); 
    } 
} 

Ancora una volta, questo è pseudo-codice. Probabilmente vedrai cosa mi viene a capo: ho questa mappa di puntatori di funzioni, ma perché ho un numero variabile di argomenti e tipi di argomenti perché voglio chiamare funzioni diverse a seconda di quello che ho ricevuto come input, quindi potrei ' Ho appena fatto questo senza una mappa e puntatori di funzioni come ti ho mostrato prima. C'è un modo per renderlo più generale, senza dover avere una clausola if-else per capire quanti argomenti passare?

Spero che tu sappia cosa ho bisogno di aiuto con :) Grazie per la lettura!

risposta

3

Una soluzione migliore sarebbe avere tutte le tue funzioni con gli stessi argomenti. Una buona idea sarebbe quella di prima completamente tokenize il tuo input (ad esempio, in un vettore di stringhe), e quindi passare tale matrice alle funzioni. È quindi possibile utilizzare un contenitore associativo (come una tabella hash o un std::map) per associare i token di comando alle funzioni del gestore.

Ad esempio:

typedef std::vector<std::string> TokenArray; 
typedef void (*CommandHandler)(const TokenArray&); 
typedef std::map<std::string, CommandHandler> CommandMap; 
void OnGo(const TokenArray& tokens) 
{ 
    // handle "go" command 
} 
void OnAttack(const TokenArray& tokens) 
{ 
    // handle "attack" command 
} 
// etc. 

CommandMap handlers; 
handlers["go"] = &OnGo; 
handlers["attack"] = &OnAttack; 
// etc. 

while(1) { 
    std::string input; 
    cin >> input; 
    std::istringstream tokenizer(input); 
    TokenArray tokens; 
    std::string token; 
    while(tokenizer >> token) // Tokenize input into whitespace-separated tokens 
    tokens.push_back(token); 
    CommandMap::iterator handler = handlers.find(tokens[0]); 
    if(handler != handlers.end()) 
     (*handler)(tokens); // call the handler 
    else 
     ; // handle unknown command 
} 
+0

+1 per una soluzione migliore della mia idea (ora che ci penso), ma se si 'typedef std :: vector TokenArray;' non dovresti dichiarare ' token nel tuo ciclo come 'TokenArray' per coerenza? –

+0

@Chris: Sì, buona telefonata, ho scritto quella parte prima di scrivere il typedef. –

+0

+1, e considera anche l'uso di un adattatore di funzione ('std :: function' da C++ 0x o' boost :: function' per i compilatori senza supporto C++ 0x), in quanto ti permetterà di usare entrambi funzioni (statiche) come nella soluzione o funzioni membro, o persino adattare differenti firme di funzioni esistenti. –

6

Invece di fare in modo che il ciclo principale legga tutti gli argomenti necessari per una funzione 'passiva', è possibile modificare il progetto per seguire il modello di comando Command e fare in modo che l'oggetto funzione/comando esegua l'analisi dell'argomento. In questo modo eviti di dover conoscere in anticipo la firma della funzione.

È possibile utilizzare la catena di responsabilità per trovare il comando corretto e lasciare che il comando consumi i token successivi.

Un esempio, utilizzando gli stream invece di strtok (diamine siamo qui in C++, giusto?) - Attenzione: non compilato, non testato, C++ ish pseudo-codice:

struct ICommand { 
    // if cmd matches this command, 
    // read all needed tokens from stream and execute 
    virtual bool exec(string cmd, istream& s) = 0; 
}; 

struct Command : public ICommand { 
    string name; 
    Command(string name):name(name){} 
    virtual bool exec(string cmd, istream& s) { 
     if(cmd != name) return false; 
     parse_and_exec(s); 
     return true; 
    } 
    virtual void parse_and_exec(istream& s) = 0; 
}; 

Un implementato comando:

struct Go : public Command { 
    Go():Command("Go"){} 

    virtual void parse_and_exec(istream& s) { 
     string direction; 
     s >> direction; 
     ... stuff with direction 
    } 
}; 

E alcuni loop principale:

ICommand* commands [] = 
{ new Go() 
, new Attack() 
, new PickUp() 
... 
, NULL // a sentinel 
}; 

string cmd; 
cin >> cmd; 
while(cmd != "quit") { 
    // find a command that can handle it 
    // note: too simple to handle erroneous input, end of stream, ... 
    for(ICommand* c = commands; c != NULL && !c->exec(cmd, cin); ++c); 
    cin >> cmd; 
} 

È possibile definire questa idea con funzioni di utilità più potenti ecc ...

Se ti aspetti una grammatica veramente difficile, potrebbe essere meglio passare a un framework di 'parser' reale, come ad es. boost::spirit.

+1

Stavo scrivendo più o meno la stessa risposta e poi ho ricevuto una telefonata, quindi mi hai battuto. Ma ricevi molte telefonate per il tuo compleanno, quindi non posso trattenerlo contro di te. +1 –

+0

@Chris Lutz: buon compleanno, vorrei invitare il tuo commento se potessi! – xtofl

+1

L'ultima parte del codice non passa il comando parsed 'cmd' a ciascuno di' ICommand', e come tale ogni comando non ha informazioni per determinare se vuole utilizzare i dati da 'istream'. È importante notare che il comando effettivo * deve * essere analizzato nel ciclo e passato ai comandi. Il motivo è che se ogni comando dovesse leggere dallo stream, non può (facilmente) lasciare l'istream nello stato in cui era prima. –

1

Avete mai pensato di andare un po 'OO. Avere una classe astratta dire "Comando" e avere classi specializzate per i comandi specifici.

struct Command 
{ 
    virtual void Execute(const string &commandline) = 0; 
}; 

struct GoCommand: Command 
{ 
    void Execute(const string &commandline) 
    { 
    // Do whatever you need here. 
    } 
} 

Avere un factory creare gli oggetti di comando in base al comando immesso dall'utente.

struct CommandFactory 
{ 
    Command *GetCommand(const string &commandName) 
    { 
    if(commandNome == "go") 
    { 
    return new GoCommand(); 
    } 
    ......... 
    ........ 
    } 
} 

Nel client ottenere l'oggetto di comando e chiamare il metodo "Execute()"

cin >> input; 
char * commandName = strtok(input, " ") 
Command *command = CommandFactory::Instance().GetCommand(commandName); 
char * params = strtok(NULL, " "); 
command->Execute(params); 
delete command; 

È possibile utilizzare il puntatore automatico per una migliore gestione della memoria.

1

Solo il parametro di funzione necessario è il resto della riga di comando. Ogni funzione dovrebbe renderlo conforme alle sue esigenze

Problemi correlati