开发者

List of functions to be called. Have to have same signature. Better implementation?

开发者 https://www.devze.com 2023-02-08 19:39 出处:网络
My program reads in \"commands\" from a text file such as \"w test.txt 5\" for write to test.txt the number 5 or \"r test.txt\" for reading from test.txt. Instead of having a horrible switch loop to m

My program reads in "commands" from a text file such as "w test.txt 5" for write to test.txt the number 5 or "r test.txt" for reading from test.txt. Instead of having a horrible switch loop to maintain I have a called aFunction which has function member

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

So I have a string name and a function pointer. Outside the class I have a

vector<aFunction> funcVec 

which holds all the functions. When I read a command in from a text file the code looks through funcVec to find the correct function to call

So when funcVec.name = command read in

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

For example I may have the funciton read(char *argsIn[], char *out) where argsIn would be an array containing test.txt, 5 and char out might be a 1 or 0 depending on whether the operation was successful.

However I don't really like t开发者_运维技巧his very much because all functions now have to have the signature (char *argsIn[], char *out) and it's up to the function to know what each parameter in the list means.

Can anyone think of a better implementation? Surely software that supports scripting has to cope with this sort of thing?


Note: you'd better use std::string and std::vector

The Command pattern is normally the way to do this, this allows "packing" the input/output arguments in the object and presenting a "blank" execution method void operator()() ensuring a common interface.

EDIT: a demonstration of Command (generic).

Define some commands:

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

Example use of two stacks of actions: those to be performed, and those that have been performed.

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


What you have to do reading these commands is threefold:

  1. Find out which function to call.
  2. Convert the list of argument strings into the arguments of the right type.
  3. Call the function passing those arguments to do the whatever needs to be done.

To abstract #2 is pretty hard, as C++ has very little support for dealing with different types that are known only at runtime, but it's not impossible.

I have once seen an article where someone used template-meta programming to find out about the parameters of registered function, and then generate the code that breaks down string list into the matching arguments. Functions were kept in a map of key strings to function pointers (using type erasure to store functions with different signatures).


Here's a sketch about how to use type erasure to store different function pointer sin a map:

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

That would leave function<F>::operator()() to chop the arguments into individual strings, convert them into the appropriate types, and then call the function with them.


Chopping the string into a list of arguments shouldn't be a problem, so I'll skip over that. The hard part is calling a function with the right parameters given that list. Note that the function's type is known within function<F>::operator()() at compile-time, so you have the whole of template-meta programming techniques at your disposal.

ISTR that article did this by creating tuples according to the function's parameter list and had the means to call any function given such a tuple. Here's you could create such tuples with recursive calls:

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
}

Then use traits to call those functions:

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


A very simple implementation would be using std::map instead of std:vector which you've used:

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

精彩评论

暂无评论...
验证码 换一张
取 消