2

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 ?

Community
  • 1
  • 1
Thalia
  • 13,637
  • 22
  • 96
  • 190

5 Answers5

3

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);
Barry
  • 286,269
  • 29
  • 621
  • 977
  • I will try this, but I do not have boost libraries... `QVariant` may work to replace `boost::any` – Thalia May 11 '15 at 18:26
  • @Thalia `QVariant` is completely unrelated to `boost::any`. `any` is a pretty simple class though, you could just look up an implementation. – Barry May 11 '15 at 18:34
  • As you point, the parameter type should exactly match, which may be error prone when you expected a const reference and simply pass a object (which is not const). – Jarod42 May 11 '15 at 18:37
2

@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/deletes anymore.

vsoftco
  • 55,410
  • 12
  • 139
  • 252
1

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);
Jarod42
  • 203,559
  • 14
  • 181
  • 302
0

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);
galinette
  • 8,896
  • 2
  • 36
  • 87
  • This looked great till I tried to build, I got `error: too many arguments to function` on the line `return it.value()(param);` – Thalia May 11 '15 at 18:20
-1

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

user666412
  • 528
  • 8
  • 18