Указатели функций 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;

вы можете использовать автоматический указатель для лучшего управления памятью.


единственный параметр функции, который вам нужен, - это остальная часть командной строки. Каждая функция должна обозначать ее в соответствии со своими потребностями