2010-11-20 4 views
9

Мне нужна помощь с C++, пожалуйста!C++ Указатели функций с неизвестным числом аргументов

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

Наиболее очевидное и простое решение этой проблемы может быть что-то вроде этого (написано в псевдокоде):

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

Я пишу в C++, так что я думал, что я мог бы решить эту проблему, используя ассоциативные карты и указатели функций. Я не знаю, как использовать указатели на функции, поэтому может возникнуть проблема. Я хочу сделать что-то вроде цикла, который ждет ввода, проанализирует введенный вход и вызовет функцию в зависимости от заданной команды. Вот некоторые C++ - иш псевдо-код, описывающий то, что я имею в виду:

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++ - псевдоиш-код. Поэтому я получил команду «go». Но теперь говорят, что я хочу иметь follwing команду:

> 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, чтобы выяснить, сколько аргументов нужно пройти?

Надеюсь, вы понимаете, что мне нужно для помощи :) Спасибо за чтение!

ответ

3

Лучшее решение состоит в том, чтобы все ваши функции принимали те же аргументы. Хорошей идеей было бы сначала полностью токенизировать ваш вход (скажем, в вектор строк), а затем передать этот массив в функции. Затем вы можете использовать ассоциативный контейнер (например, хеш-таблицу или 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 
} 
+0

+1 для лучшего решения, чем моя идея (теперь, когда я думаю об этом), но если вы 'typedef std :: vector TokenArray;' вам не следует объявлять ' tokens' в вашем цикле как «TokenArray» для согласованности? –

+0

@ Крис: Да, хороший звонок, я написал эту часть, прежде чем я написал typedef. –

+0

+1, а также рассмотреть возможность использования функционального адаптера ('std :: function' из C++ 0x или' boost :: function' для компиляторов без поддержки C++ 0x), так как это позволит вам использовать либо бесплатный (статические) функции, как в ваших решениях или функциях-членах, или даже адаптировать различные существующие сигнатуры функций. –

6

Вместо того, чтобы ваша основная петля считывала все аргументы, необходимые для «пассивной» функции, вы можете изменить свой дизайн, чтобы следовать шаблону проектирования команды, и ваш объект function/command выполняет разбор аргумента. Таким образом, вы избегаете необходимости знать подпись функции.

Вы можете использовать Цепь Ответственности, чтобы найти правильную команду, и пусть команда потребляет следующие маркеры.

Пример, используя потоки вместо strtok (черт возьми, мы C++ здесь, правильно?) - предупреждение: Неоткомпилированный, непроверенный, C++ МОГ псевдо-код:

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.

+1

Я писал почти такой же ответ, а затем получил телефонный звонок, поэтому вы избили меня. Но вы получаете много телефонных звонков в свой день рождения, поэтому я не могу держать это против вас. +1 –

+0

@ Крис Лутц: с днем ​​рождения, я бы поднял ваш комментарий, если бы мог! – xtofl

+1

Последняя часть кода не передает синтаксическую команду 'cmd' в каждую из« ICommand », и поэтому каждая команда не имеет никакой информации, чтобы определить, хочет ли она потреблять данные из' istream'. Важно отметить, что фактическая команда * должна * анализироваться в цикле и передаваться в commmands. Причина в том, что если каждая команда должна была читать из потока, она не может (легко) оставить 'istream' в том же состоянии, что и раньше. –

1

Вы думали о поездке немного OO. Попросите абстрактный класс сказать «Command» и иметь специальные классы для конкретных команд.

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

В клиенте получить объект команды и называют «Execute()» методу

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

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

1

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

Смежные вопросы