2010-11-20 20 views
9

¡Necesito ayuda con C++, por favor!C++ Punteros de función con un número desconocido de argumentos

Estoy escribiendo un analizador de comandos para un pequeño juego de texto, y me he encontrado con algunos problemas. El analizador debe leer y analizar los comandos ingresados ​​por el jugador.

La solución más obvia y sencilla de esto podría ser algo como esto (escrito en pseudo-código):

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

estoy escribiendo en C++, así que estaba pensando que podría solucionar esto mediante el uso de una mapa asociativo y punteros de función. No estoy tan familiarizado con el uso de indicadores de funciones, por lo que puede ser por eso que estoy teniendo problemas. Lo que quiero hacer es tener algún tipo de bucle que espere la entrada, analizar la entrada que se inserta y llamar a una función dependiendo del comando dado. Aquí hay algo de C++ - ish pseudo-código que describe lo que estoy pensando:

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

así que espero que me hago un poco claro en lo que quiero que suceda. El jugador podría haber tenido algo de entrada como

> go south 

y podría haber terminado el código con algo como:

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

Básicamente, el valor devuelto por el mapa sería la función que realiza el comando " ir ", y esa función aparentemente toma un argumento, el destino. De nuevo, este es un código C++ - pseudo-ish. Así que obtuve el comando "ir" cubierto. Pero ahora digo que yo quiero tener el comando siguientes aparatos:

> attack troll with sword 

Ahora me siento que tengo que hacer algo como:

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); 
    } 
} 

Una vez más, esto es pseudo-código. Probablemente vea lo que me cuelga: tengo este mapa de indicadores de funciones, pero debido a que tengo un número variable de argumentos y tipos de argumentos porque quiero llamar diferentes funciones dependiendo de lo que obtuve como entrada, entonces pude ' He hecho esto sin un mapa y los indicadores de función que te mostré primero. ¿Hay alguna forma en que pueda hacer esto más general, sin tener que tener alguna cláusula if-else para averiguar cuántos argumentos aprobar?

Espero que entiendas lo que necesito ayuda :) Gracias por leer!

Respuesta

3

Una mejor solución sería que todas sus funciones tomen los mismos argumentos. Una buena idea sería primero tokenizar por completo su entrada (por ejemplo, en un vector de cadenas), y luego pasar esa matriz a las funciones. Luego puede usar un contenedor asociativo (como una tabla hash o std::map) para asignar tokens de comando a las funciones del manejador.

Por ejemplo:

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 para una mejor solución que mi idea (ahora que lo pienso), pero si 'typedef std :: vector TokenArray;' no deberías declarar ' tokens' en su bucle como un 'TokenArray' por coherencia? –

+0

@Chris: Sí, buena llamada, escribí esa parte antes de escribir el typedef. –

+0

+1, y también considere usar un adaptador de función ('std :: function' de C++ 0x o' boost :: function' para compiladores sin compatibilidad con C++ 0x), ya que eso le permitirá usar free funciones (estáticas) como en su solución o funciones miembro, o incluso adaptar diferentes firmas de funciones existentes. –

6

En lugar de tener su loop principal leyendo todos los argumentos necesarios para una función 'pasiva', puede cambiar su diseño para seguir el patrón de diseño Command y hacer que su objeto function/command realice el análisis del argumento. De esta forma evitará tener que saber la firma de la función por adelantado.

Puede usar la Cadena de responsabilidad para encontrar el Comando correcto y dejar que el Comando consuma los próximos tokens.

Un ejemplo, usando streams en lugar de strtok (diablos, somos C++ aquí, ¿no?) - advertencia: sin compilar, no probado, C++ ish pseudo-código:

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 implementado comando:

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

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

Y algunos bucle principal:

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; 
} 

Puede refinar este idea con funciones de utilidad más fuertes, etc. ...

Si espera una gramática realmente difícil, puede ser mejor pasar a un marco analizador 'real', como p. boost::spirit.

+1

Estaba escribiendo casi la misma respuesta y luego recibí una llamada telefónica, por lo que me ganaste. Pero recibes muchas llamadas telefónicas el día de tu cumpleaños, así que no puedo mantener eso en tu contra. +1 –

+0

@Chris Lutz: feliz cumpleaños hombre, ¡podría votarte si pudieras! – xtofl

+1

La última parte del código no pasa el comando analizado 'cmd' a cada uno de' ICommand', y como tal, cada comando no tiene información para determinar si desea consumir datos del 'istream'. Es importante tener en cuenta que el comando real * debe * analizarse en el ciclo y pasarse a los comandos. La razón es que si cada comando fuera a leer de la secuencia, no puede (fácilmente) dejar 'istream' en el mismo estado que antes. –

1

¿Ha pensado en ir un poco OO. Haga que una clase abstracta diga "Comando" y tenga clases especializadas para los comandos específicos.

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

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

Haga que una fábrica cree los objetos de comando en función del comando introducido por el usuario.

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

En el cliente consigue el objeto de comando y llamar al método "Ejecutar()"

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

Puede utilizar el puntero de auto para una mejor gestión de memoria.

1

El único parámetro de función que necesita es el resto de la línea de comandos. Cada función debe tokenizar de acuerdo con sus necesidades

Cuestiones relacionadas