Указатели функций C++ с неизвестным числом аргументов
мне нужна помощь с C++, пожалуйста!
Я пишу парсер команд для небольшой текстовой игры, и у меня возникли некоторые проблемы. Парсер должен считывать и анализировать команды, введенные игроком.
наиболее очевидным и простым решением этого может быть что-то вроде этого (написано в псевдо-код):
command <- read input from the player
if command == COMMAND1
do command1
else if command == COMMAND 2
do command2
...
Я пишу на C++, поэтому я думал, что могу решить эту проблему с помощью ассоциативной карты и указателей функций. Я не это знакомо с использованием указателей функций, так что, возможно, поэтому у меня проблемы. Что я хочу сделать, это иметь какой-то цикл, который ждет ввода, разбора, который вводится, и вызывает функцию, в зависимости от заданной команды. Вот некоторый псевдо-код c++-ish, описывающий, что я думаю:
while(1) {
cin >> input;
char * tok = strtok(input, " ")
functionpointer fptr = command_map.find(tok);
... // here, I get stuck on what to do..
}
поэтому я надеюсь, что я сделаю себя несколько ясным о том, что я хочу, чтобы произошло. Игрок мог бы ввести что-то вроде
> go south
и я мог бы закончить код с чем-то вроде:
destination = strtok(NULL, " ");
fptr(destination);
в принципе, значение, возвращаемое с карты, будет функцией, которая выполняет команду "go", и эта функция, по-видимому, принимает один аргумент-пункт назначения. Опять же, это некоторый код C++-псевдо-ish. Поэтому я получил команду "go". Но теперь скажите, что я хочу иметь следующую команду:
> attack troll with sword
Я чувствую, что мне нужно сделать что-то вроде:
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);
}
}
опять же, это псевдо-код. Вы, наверное, видите, что У меня есть эта карта указателей функций, но потому что у меня есть переменное количество аргументов и тип аргументов, потому что я хочу вызывать разные функции в зависимости от того, что я получил в качестве ввода, поэтому я мог бы просто сделать это без указателей карты и функции, как я показал вам сначала. Есть ли способ сделать это более общим, не имея некоторого предложения if-else, чтобы выяснить, сколько аргументов нужно передать?
надеюсь, вы понимаете, с чем мне нужна помощь :) Спасибо за чтение!
4 ответов
лучшим решением было бы, чтобы все ваши функции принимали одни и те же аргументы. Хорошей идеей было бы сначала полностью обозначить ваш ввод (скажем, в вектор строк), а затем передать этот массив функциям. Затем вы можете использовать ассоциативный контейнер (например, хэш-таблицу или std::map
) для сопоставления маркеров команд с функциями обработчика.
например:
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
}
вместо того, чтобы ваш основной цикл считывал все аргументы, необходимые для "пассивной" функции, вы можете изменить свой дизайн, чтобы следовать шаблону дизайна команды, и заставить ваш объект функции/команды выполнять разбор аргументов. Таким образом, вам не нужно знать подпись функции заранее.
вы можете использовать цепочку ответственности, чтобы найти правильную команду, и пусть команда потреблять следующие маркеры.
пример использования потоков вместо strtok (heck мы здесь на С++, верно?)- предупреждение: несжатый, непроверенный, c++ish псевдо-код:
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;
};
используется команды:
struct Go : public Command {
Go():Command("Go"){}
virtual void parse_and_exec( istream& s ) {
string direction;
s >> direction;
... stuff with direction
}
};
и некоторые основные петли:
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;
}
вы можете уточнить эту идею с более сильными функциями полезности и т. д...
Если вы ожидаете действительно жесткой грамматики, может быть, лучше перейти к "реальной" структуре парсера, например boost:: spirit.
вы думали о том, чтобы пойти немного ОО. У абстрактного класса есть "команда" и специализированные классы для конкретных команд.
struct Command
{
virtual void Execute( const string &commandline ) = 0;
};
struct GoCommand: Command
{
void Execute( const string &commandline )
{
// Do whatever you need here.
}
}
есть фабрика создать объекты команды на основе команды, введенной пользователем.
struct CommandFactory
{
Command *GetCommand( const string &commandName )
{
if( commandNome == "go" )
{
return new GoCommand();
}
.........
........
}
}
в клиенте получите объект command и вызовите метод "Execute ()"
cin >> input;
char * commandName = strtok(input, " ")
Command *command = CommandFactory::Instance().GetCommand( commandName );
char * params = strtok(NULL, " ");
command->Execute( params );
delete command;
вы можете использовать автоматический указатель для лучшего управления памятью.
единственный параметр функции, который вам нужен, - это остальная часть командной строки. Каждая функция должна обозначать ее в соответствии со своими потребностями