C++: Generic factory, который может вызвать любой конструктор?

Я пытаюсь написать класс фабрики, который будет иметь стандартный интерфейс, который выглядит так:

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

сейчас звонит:

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

вызовет конструктор MyDerived(1,2,3) и возвращает указатель на созданный объект

это звучит как то, что должно быть возможно с C++11, но я не мог понять, как это сделать.
Начиная со стандартной фабрики стирания типа:

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);
    }
};

проблема здесь в том, что когда-то вы сотрите тип созданного объекта, вы больше не можете передавать произвольные переданные аргументы шаблона, так как вам нужно передать их через виртуальную функцию.

ответ на этот вопрос:
как передать указатель функции, который указывает на конструктор?
говорит о чем-то подобном, но ответ должен пройти через функцию, которая специфична для каждого производного класса. Я хочу использовать конструктор класса напрямую и не писать create() функция.

1 ответов


Я не знаю, почему ваше отвращение к написанию . Вот один из них, который я реализовал.

#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;
}

примечание: Я не делал все, что делает ваш, я просто реализовал функцию create. Я оставляю окончательное решение за вами.

Это использует совершенную переадресацию, чтобы позволить шаблону varidict передавать любое количество параметров конструктору. Затем функция register может хранить указатель функции конкретного экземпляра шаблона для определенный набор параметров.

редактировать

Я забыл использовать соответствующие forward<ARGs>(args)... вызов для реализации идеальной переадресации. Теперь он добавлен.

Что касается вас, думающих, что это не полезно, вот полная реализация вашей фабрики с использованием совершенных шаблонов пересылки и varidict, позволяющих определенное количество параметров определенных типов для конкретного экземпляра фабрики:

#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;
}

это то, что вы хотите? Если вы не хотите разберитесь с параметрами функции, используйте вспомогательную функцию шаблона, чтобы вывести их для вас так:

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;
}

который имеет дополнительный бонус, позволяющий несколько сигнатур конструктора, доступных через кажущийся API одной функции (он только выглядит как один, но на самом деле N здесь N - это количество различных подписей вы разрешить). Это все можно посмотреть через это онлайн демо.

вы все еще будете использовать такая же регистрация, как я изображал раньше, хотя, которая может быть сокращена с помощью макроса.

если это еще не то, что вы хотите, а затем добавьте дополнительные детали к вашему вопросу.