Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++: Generic factory that can call any constructor?

I'm trying to write a factory class which will have a standard interface that looks like this:

Register<MyBase, MyDerived> g_regDerived("myderived");  // register to factory

now calling:

auto* d = Factory<MyBase>::instance().create("myderived", 1, 2, 3);

will invoke the constructor MyDerived(1,2,3) and return a pointer to the created object

This sounds like something that should be possible with C++11 but I could not figure out how to do it.
Starting from a standard type-erasure factory:

template<typename BaseT>
class Factory {
public:
    static Factory* instance() {
        static Factory inst;
        return &inst;
    }
    template<typename T>
    void reg(const string& name) {
        m_stock[name].reset(new Creator<T>);
    }
    BaseT* create(const string& name) {
        return m_stock[name]->create();
    }
private:
    struct ICreator {
        virtual BaseT* create() = 0;
    };
    template<typename T>
    struct Creator : public ICreator {
        virtual BaseT* create() {
            return new T;
        }
    };
    std::map<string, std::unique_ptr<ICreator>> m_stock;
};

template<typename BaseT, typename T>
class Register {
public:
    Register(const QString& name) {
        Factory<BaseT>::instance()->reg<T>(name);
    }
};

The problem here is fact that once you erase the type of the created object, you can no longer pass arbitrary template forwarded arguments since you need to pass them through a virtual function.

The answer to this question:
How to pass a function pointer that points to constructor?
talks about something similar but the answer there is to go through a function which is specific for every derived class. I want to use the class constructor directly and not have to write a create() function.

like image 549
shoosh Avatar asked Mar 19 '15 10:03

shoosh


1 Answers

I don't know why your aversion to writing a create() function. So here is one that I implemented.

#include <iostream>
#include <utility>

using namespace std;

class C
{
public:
    virtual char const* whoAmI() const = 0;
};

class A : public C
{
public:
    A(int a1)
    {
        cout << "A(" << a1 << ")" << endl;
    }
    
    A(float a1)
    {
        cout << "A(" << a1 << ")" << endl;
    }
    
    virtual char const* whoAmI() const override
    {
        return "A";
    }
};

class B : public C
{
public:
    B(int a1)
    {
        cout << "B(" << a1 << ")" << endl;
    }
    
    B(float a1)
    {
        cout << "B(" << a1 << ")" << endl;
    }

    virtual char const* whoAmI() const override
    {
        return "B";
    }

};

template<typename BASET>
class Factory
{
public:
    // could use a is_base type trait test here
    template <typename T, typename...ARGs>
    static BASET* create(ARGs&&...args)
    {
        return new T(forward<ARGs>(args)...);
    }

};
int main()
{
   Factory<C> factory;
   C* a = factory.create<A>(1);
   C* b = factory.create<B>(1.0f);
   cout << a->whoAmI() << endl;
   cout << b->whoAmI() << endl;
   return 0;
}

NOTE: I didn't do everything that yours does, I merely implemented the create function. I leave the final implementation up to you.

This uses perfect forwarding to enable a varidict template to pass any number of parameters to a constructor. Your register function can then store a function pointer of a particular template instance, for a particular parameter set.

EDIT

I forgot to use the appropriate forward<ARGs>(args)... call to implement perfect forwarding. It has now been added.

As for you thinking that this is not useful, here is the full implementation of your factory using perfect forwarding and varidict templates allowing a specific number of parameters of particular types for a particular factory instance:

#include <string>
#include <map>
#include <memory>
#include <utility>
#include <iostream>

using namespace std;

    template<typename BaseT, typename...ARGs>
    class Factory {
    public:
        static Factory* instance() {
            static Factory inst;
            return &inst;
        }
        template<typename T>
        void reg(const string& name) {
            m_stock[name].reset(new Creator<T>);
        }
        BaseT* create(const string& name, ARGs&&...args) {
            return m_stock[name]->create(forward<ARGs>(args)...);
        }
    private:
        struct ICreator
        {
            virtual BaseT* create(ARGs&&...) = 0;
            
        };
        template<typename T>
        struct Creator : public ICreator {
            virtual BaseT* create(ARGs&&...args) override
            {
                return new T(forward<ARGs>(args)...);
            }
        };
        std::map<string, std::unique_ptr<ICreator>> m_stock;
    };
    
    template<typename BaseT, typename T, typename...ARGs>
    class Register {
    public:
        Register(const string& name) {
            auto instance = Factory<BaseT, ARGs...>::instance();
            instance->template reg<T>(name);
        }
    };

struct C
{
    virtual char const * whoAmI() const = 0;
};

struct A : public C
{
    A(int a1, int a2)
    {
        cout << "Creating A(" << a1 << ", " << a2 << ")" << endl;
    }
    
    virtual char const * whoAmI() const override
    {
        return "A";
    }
};

struct B : public C
{
    B(int b1, int b2)
    {
        cout << "Creating B(" << b1 << ", " << b2 << ")" << endl;
    }
    B(int b1, int b2, int b3)
    {
        cout << "Creating B(" << b1 << ", " << b2  << ", " << b3 << ")" << endl;
    }
    virtual char const * whoAmI() const override
    {
        return "B";
    }
};

typedef int I;
Register<C, A, I, I> a("a");
Register<C, B, I, I> b("b");
Register<C, B, I, I, I> b3("b");
int main()
{
    C* a = Factory<C, I, I>::instance()->create("a", 1, 2);
    C* b = Factory<C, I, I>::instance()->create("b", 3, 4);
    C* b3 = Factory<C, I, I, I>::instance()->create("b", 5, 6, 7);
    cout << "I am a " << a->whoAmI() << endl;
    cout << "I am a " << b->whoAmI() << endl;
    cout << "I am a " << b3->whoAmI() << endl;
    return 0;
}

Is that what you want? If you don't want to deal with the function parameters, use a helper template function to deduce them for you like so:

template <typename BaseT, typename...ARGs>
BaseT* create(const string& name, ARGs&&...args)
{
    return Factory<C, ARGs...>::instance()->create(name, forward<ARGs>(args)...);
}

int main()
{
    C* a = create<C>("a", 1, 2);
    C* b = create<C>("b", 3, 4);
    C* b3 = create<C>("b", 3, 4, 5);
    cout << "I am a " << a->whoAmI() << endl;
    cout << "I am a " << b->whoAmI() << endl;
    cout << "I am a " << b3->whoAmI() << endl;
    return 0;
}

Which has the added bonus of allowing multiple constructor signatures available through the apparent single function API (it only looks like one, but is actually N where N is the number of different signatures you allow). This all can be viewed through this online demo.

You'll still need to use the same registration as I depicted before though, which could be shortened by way of a macro.

If this is still not what you want, then add additional detail to your question.

like image 178
Adrian Avatar answered Nov 11 '22 23:11

Adrian