Reputation: 14635
I am using a class factory to create objects dynamically. I used this answer for its simplicity (and because I am using Qt).
But now I realize I must add an argument to my constructor
Item(bool newItem /* = true*/);
instead of
Item();
for the code in the referred answer:
template <typename T>
class ClassFactory
{
public:
template <typename TDerived>
void registerType(QString shape)
{
_createFuncs[shape] = &createFunc<TDerived>;
}
T* create(QString shape)
{
typename QMap<QString, PCreateFunc>::const_iterator it = _createFuncs.find(shape);
if (it != _createFuncs.end())
{
return it.value()();
}
return NULL;
}
private:
template <typename TDerived>
static T* createFunc()
{
return new TDerived();
}
typedef T* (*PCreateFunc)();
QMap<QString, PCreateFunc> _createFuncs;
};
I registered the class
classFactory.registerType <Type1_Item> ("type1");
when needed, I called
Item* item = classFactory.create("type1");
I am trying to add an additional argument in the class factory, to represent the constructor argument, but my attempts all result in error.
Why do I need it : simple case:
create a new object - sets defaults; for certain objects, it requires an open file dialog since data has to be loaded from a file.
load an object - fills data, including the filename for objects that contain file info
To be able to call the "load" function, an object must exist - which means that if I create a new object, I will trigger an open file dialog even though I do not need it.
The work around that I see is, to have a constructor followed by a setup function. But... that means constructing an object always requires a 2-function call, which seems like bad design.
that is why I am looking for a way to register and call the classes using simple calls like
classFactory.registerType <Type1_Item> ("type1", bool);
Item* item = classFactory.create("type1", true);
Is it possible, and how can I do it ?
Upvotes: 2
Views: 1115
Reputation: 304142
The one way I can think of involves requiring that the arguments match exactly. First, we're going to store our functions using boost::any
. This is because they may have different types, so we need a heterogenous container:
QMap<QString, boost::any> _createFuncs;
Our register
function will create a specific function pointer to store in said any
:
template <typename TDerived, typename... T>
void registerType(QString shape)
{
_createFuncs[shape] = &createFunc<TDerived, T...>;
}
where createFunc
now takes extra arguments:
template <typename TDerived, typename... Args>
static T* createFunc(Args... args)
{
return new TDerived(args...);
}
The key is what we do on the creation side. We need to check to see if the any
we have stored for the particular type is the right type:
template <typename... Args>
T* create(QString shape, Args... args)
{
using FPtr = T*(*)(Args...);
auto it = _createFuncs.find(shape);
if (it != _createFuncs.end())
{
// ok, it points to some any. is it the right any?
FPtr* fptr = boost::any_cast<FPtr>(&it.value());
if (fptr) {
return (*fptr)(args...);
}
// alternatively to the above, we can have createFunc
// throw bad_any_cast if you pass the wrong arguments
// which could be a loud, explicit failure rather than
// a silent one
return boost::any_cast<FPtr>(it.value())(args...);
}
return nullptr;
}
That will allow this to work:
classFactory.registerType<Item, bool>("type1");
^^^^^^
arg list
Item* item = classFactory.create("type1", true);
Item* item2 = classFactory.create<bool>("type1", 1);
But this will fail, since the any
takes a bool
, not an int
:
Item* item3 = classFactory.create("type1", 1);
Upvotes: 3
Reputation: 518
I've done this using C++11 parameter packs:
// pack.cc
#include <utility>
template<class T, typename... P>
T * create(P&&... params)
{
return new T(std::forward<P>(params)...);
}
class X
{
public:
X() {}
};
class Y
{
public:
Y(int) {}
};
int main()
{
X * x = create<X>();
Y * y = create<Y>(1);
delete x;
delete y;
}
Compile this example g++ -std=c++11 -o pack pack.cc
Upvotes: -1
Reputation: 218323
You may use this modified version
template <typename T, typename ... Ts>
class ClassFactory
{
public:
template <typename TDerived>
void registerType(QString shape)
{
_createFuncs[shape] = &createFunc<TDerived>;
}
T* create(QString shape, Ts... args)
{
typename QMap<QString, PCreateFunc>::const_iterator it = _createFuncs.find(shape);
if (it != _createFuncs.end())
{
return it.value()(args...);
}
return nullptr;
}
private:
template <typename TDerived>
static T* createFunc(Ts... args)
{
return new TDerived(args);
}
typedef T* (*PCreateFunc)(Ts...);
QMap<QString, PCreateFunc> _createFuncs;
};
And use it
ClassFactory<Item, bool> classFactory;
classFactory.registerType <Type1_Item> ("type1");
Item* item = classFactory.create("type1", true);
Upvotes: 1
Reputation: 56577
@Barry's answer is more than complete. However, if you are just interested in a simplified factory that can construct objects that have constructors taking different parameters, you can do something like:
// Factory method for classes having constructors
// that take an arbitary number of parameters
#include <memory>
class Factory
{
public:
template<typename T, typename... Params>
static std::unique_ptr<T> create(Params... params)
{
return std::make_unique<T>(params...);
}
};
struct Foo
{
Foo(int) {};
};
struct Bar
{
Bar(bool, double) {};
};
int main()
{
std::shared_ptr<Foo> foo = Factory::create<Foo>(42);
std::shared_ptr<Bar> bar = Factory::create<Bar>(true, 42.5);
}
Note that I used smart pointers here, so you don't need to keep track of new/delete
s anymore.
Upvotes: 2
Reputation: 9330
If all the objects have the same parameter types (here a bool), just change the create function like this:
T* create(QString shape, bool param) //modified
{
typename QMap<QString, PCreateFunc>::const_iterator it = _createFuncs.find(shape);
if (it != _createFuncs.end())
{
return it.value()(param); //modified
}
return NULL;
}
And change createFunc also:
static T* createFunc(bool param)
{
return new TDerived(param);
}
typedef T* (*PCreateFunc)(bool);
Upvotes: 0