Список функций для вызова. Должен иметь одинаковую подпись. Лучшая реализация? - PullRequest
1 голос
/ 04 февраля 2011

Моя программа считывает «команды» из текстового файла, такого как «w test.txt 5» для записи в test.txt, число 5 или «r test.txt» для чтения из test.txt.Вместо того, чтобы поддерживать ужасный цикл переключения, у меня есть вызываемая aFunction, у которой есть функция-член

string name;
void (*thefunction)(char *argsIn[], char *out); 

Так что у меня есть строковое имя и указатель на функцию.Вне класса у меня есть

vector<aFunction> funcVec 

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

Так что когда funcVec.name = команда прочитана в

(*funcVec[i].theFunction(other values from the text file, output);

Например, яесть функция read(char *argsIn[], char *out), где argsIn будет массивом, содержащим test.txt, 5 и char могут быть равны 1 или 0 в зависимости от того, была ли операция успешной.

Однако мне это не очень нравится.во многом потому, что теперь все функции должны иметь сигнатуру (char * argsIn [], char * out), и функция должна знать, что означает каждый параметр в списке.

Может кто-нибудь придумать лучшую реализацию?Конечно, программное обеспечение, которое поддерживает сценарии, должно справляться с подобными вещами?

Ответы [ 3 ]

3 голосов
/ 04 февраля 2011

Примечание: вам лучше использовать std::string и std::vector

Обычно для этого используется шаблон Command, это позволяет "упаковывать" аргументы ввода / вывода в объекте и представлять "пустой" метод выполнения void operator()(), обеспечивая общий интерфейс.

РЕДАКТИРОВАТЬ: демонстрация команды (общая).

Определите некоторые команды:

struct Command: boost::noncopyable
{
  virtual void do() = 0;
  virtual void undo() = 0;
  virtual ~Command() {}
};

class SaveFile: public Command
{
public:
  explicit SaveFile(FileHandle f, Changes c): _file(file), _changes(c) {}

  virtual void do() { _file.apply(_changes); }
  virtual void undo() { _file.revert(_changes); }

private:
  FileHandle _file;
  Changes _changes;
};

class OpenFile: public Command
{
public:
  explicit OpenFile(std::string filename): _filename(filename) {}

  FileHandle get() const { return _file; }

  virtual void do() { _file.load(_filename); }
  virtual void undo() { /*nothing to be done*/ }

private:
  std::string _filename;
  FileHandle _file;
};

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

typedef std::stack<std::unique_ptr<Command>> CommandStack;

void transaction(CommandStack& todo)
{
  CommandStack undo;
  try
  {
    while(!todo.empty())
    {
      todo.top()->do();
      undo.push(std::move(todo.top()));
      todo.pop();
    }
  }
  catch(std::exception const&)
  {
    while(!undo.empty())
    {
      undo.top()->do();
      undo.pop();
    }
  }
} // transaction
1 голос
/ 04 февраля 2011

Чтение этих команд нужно сделать в три раза:

  1. Узнайте какую функцию вызвать.
  2. Преобразовать список строк аргументов в аргументы правильного типа .
  3. Вызовите функцию , передав эти аргументы, чтобы сделать все, что нужно сделать.

Абстрагироваться от # 2 довольно сложно, поскольку в C ++ очень мало поддержки для работы с различными типами, которые известны только во время выполнения, но это не невозможно.

Однажды я видел статью, в которой кто-то использовал шаблонное мета-программирование, чтобы узнать о параметрах зарегистрированной функции, а затем сгенерировал код, который разбивает список строк на соответствующие аргументы. Функции сохранялись в карте строк ключей для указателей на функции (используя стирание типов для хранения функций с разными сигнатурами).


Вот эскиз о том, как использовать стирание типов для хранения различных указателей функций на карте:

struct func_base {
    virtual void operator()(std::istream&)  const = 0;
};

template< typename F >
class function : public func_base {
public:
    function(F f) : func_(f) {}
    void operator()(std::string& arguments) const;
private:
    func_base func_;
};

typedef std::map< std::string, std::shared_ptr<func_base> >  func_map; 

template< typename F >
void addFunc(func_map& map, const std::string& keyword, F f)
{
    assert(map.find(keyword) == map.end());
    map[keyword] = std::shared_ptr<func_base>(new function<T>(f));
}

Это позволило бы function<F>::operator()() разделить аргументы на отдельные строки, преобразовать их в соответствующие типы и затем вызвать функцию с ними.


Добавление строки в список аргументов не должно быть проблемой, поэтому я пропущу это. Трудной частью является вызов функции с правильными параметрами, заданными в этом списке. Обратите внимание, что тип функции известен в function<F>::operator()() во время компиляции , поэтому в вашем распоряжении есть все методы шаблонного мета-программирования.

В этой статье ISTR сделал это, создав кортежи в соответствии со списком параметров функции, и имел средства для вызова любой функции с таким кортежем. Вот вы могли бы создать такие кортежи с рекурсивными вызовами:

template< typename Tuple >
Tuple convert_args(const std::string& line)
{
  Tuple result;
  // I don't know tuples well enough yet, so here's just an 
  // algorithm rather than code:


  // 1. read first argument from line and put it into tuple's first element
  // 2. call yourself for a tuple that consists of the remaining elements of Tuple
  // 3. fill the remaining tuple elements from the result of #2

  return result
}

Затем используйте черты для вызова этих функций:

template<typename F>
struct func_traits;

template<typename R, typename A1>// function taking one arg
struct func_traits<R(*)()> {
  typedef std::tuple<A1> arg_list;

  static R call(R(*f)(), const arg_list& args)
  {
    return f(std::get<0>(arg_list)); // how do you access an element in a tuple
  }
};

template<typename R, typename A1, typename A2>// function taking two args
struct func_traits<R(*)()> {
  typedef std::tuple<A1,A2> arg_list;

  static R call(R(*f)(), const arg_list& args)
  {
    return f(std::get<0>(arg_list), std::get<1>(arg_list));
  }
};

// repeat for as many args as you'll need
0 голосов
/ 04 февраля 2011

Очень простая реализация будет использовать std::map вместо std:vector, который вы использовали:

typedef void (*FunctionType)(char *argsIn[], char *out); 

std::map<std::string, FunctionType> functionMap;

//populate the functionMap
//key = commandName, value = function to be called;
functionMap["write"] = WriteFunc;
functionMap["read"]= ReadFunc;

//later use this map as
functionMap[commandName](arguments, output);
...