There is an interface( or class with all pure virtual functions) ITask. I wanted to create many task classes which adhere to this interface. The project demanded that the task objects to be created and used (and executed) in a uniform manner. Since every task might take different type and number of arguments, I had to find a way to create an container which can store objects of different types. The problem is that STL containers like vector
In terms of code I wanted something like this:
// Lets say I have a task which creates report
// by accessing tables.
// So the inputs to the task are:
// 1. Database connection
// 2. Output file name
class CreateReport : public ITask
{
public:
// I did not want seperate class like ArgumentList for
// each Task class. The same class should be used to pass
// the arguments for all task classes. This is what I mean
// by "uniform manner" mentioned earlier.
void run(const ArgumentList* args) // Inherited from ITask
{
...
DBConnection* conn = args->getValue("connection");
string fileName = args->getValue("filename");
...
}
...
};
// Using CreateReport
...
ArgumentList* a = new ArgumentList();
a.add(new DBConnection(connectionString));
a.add("report.txt");
ITask* t = new CreateReport();
t->run(a);
When I came up with a solution, it was very obvious and I thought "Why could'nt I think this before?"
Solution >>
Now, if you know Boost, you must be thinking "Well, there are tuples which can be used to store multiple types of objects". The issue is that in my case I don't know at compile time how many arguments are going to be passed, so using tuple is not an option. I will discuss pros and cons of my solution & tuples later below. First the solution:
// Interface
class IArgument
{
public:
virtual string getName() = 0;
virtual ~IArgument(){}
};
template
class Argument : public IArgument
{
public:
Argument(string i_name, VALUE_TYPE i_value)
:m_name(i_name), m_value(i_value)
{
}
/// Get the argument name
virtual string getName() { return m_name; }
VALUE_TYPE getValue() { return m_value; }
protected:
string m_name;
VALUE_TYPE m_value;
};
class NamedArguments
{
public:
/// Gets the value by name
template
VALUE_TYPE getValue(string i_argName)
{
map::iterator itr;
if( (itr = m_argMap.find(i_argName)) == m_argMap.end() )
THROW_AMR_EXCEPTION_2(ArgumentNotFound, i_argName);
Argument* arg = NULL;
if( (arg = dynamic_cast< Argument* >(itr->second)) == NULL)
THROW_EXCEPTION(InvalidArgumentType);
return (VALUE_TYPE)arg->getValue();
}
/// Adds the argument and value
template
void add(string i_argName, VALUE_TYPE i_value)
{
map::iterator itr;
// Ignore if the argument is already present
if( (itr = m_argMap.find(i_argName)) != m_argMap.end() )
return;
m_argMap.insert
(
map::value_type
(
i_argName, new Argument(i_argName, i_value)
)
);
//m_argMap[i_argName] = new Argument(i_argName, i_value);
}
/// DTor
~NamedArguments()
{
for(map::iterator itr = m_argMap.begin();
itr != m_argMap.end(); itr++ )
delete itr->second;
}
private:
mapm_argMap;
//mapm_argNames;
};
//
// And ... this is how its used??
//
NamedArguments n;
n.add<DBConnection*>("connection", conn);
n.add<string>("filename",string("report.txt"));
CreateReport r;
r.run(&n);
// ... The CreateReport::run() will look like
void CreateReport::run(const NamedArguments* args)
{
...
DBConnection* conn = args->getValue<DBConnection*>("connection");
string filename = args->getValue<string>("filename");
...
}
Pros and cons of this solution and tuple to follow...