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:
- Find out which function to call.
- Convert the list of argument strings into the arguments of the right type.
- 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);
精彩评论