Могу ли я создать фабрику классов для конструктора с параметром?

Я использую фабрику классов для динамического создания объектов. Я использовал этот ответ для простоты (и потому что я использую Qt).

Но теперь я понимаю, что должен добавить аргумент в свой конструктор

Item(bool newItem /* = true*/);

вместо

Item();

для кода в приведенном ответе:

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

Я зарегистрировал класс

classFactory.registerType <Type1_Item> ("type1");

при необходимости звонил

Item* item = classFactory.create("type1");

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

Зачем мне это нужно: простой случай:

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

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

Чтобы иметь возможность вызывать функцию "load", объект должен существовать - это означает, что, если я создаю новый объект, я вызову диалог открытия файла, даже если он мне не нужен.

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

Вот почему я ищу способ зарегистрироваться и вызвать классы с помощью простых вызовов, таких как

classFactory.registerType <Type1_Item> ("type1", bool);
Item* item = classFactory.create("type1", true);

Возможно ли это, и как я могу это сделать?

5 ответов

Решение

Вы можете использовать эту модифицированную версию

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

И использовать это

ClassFactory<Item, bool> classFactory;
classFactory.registerType <Type1_Item> ("type1");

Item* item = classFactory.create("type1", true);

Один из способов, который я могу придумать, состоит в том, чтобы требовать, чтобы аргументы точно совпадали. Во-первых, мы собираемся хранить наши функции, используя boost::any, Это потому, что они могут иметь разные типы, поэтому нам нужен гетерогенный контейнер:

QMap<QString, boost::any> _createFuncs;

наш register Функция создаст указатель конкретной функции для хранения в указанном any:

template <typename TDerived, typename... T>
void registerType(QString shape)
{
    _createFuncs[shape] = &createFunc<TDerived, T...>;
}

где createFunc теперь принимает дополнительные аргументы:

template <typename TDerived, typename... Args>
static T* createFunc(Args... args)
{
    return new TDerived(args...);
}

Ключ в том, что мы делаем на стороне творения. Нам нужно проверить, если any мы сохранили для конкретного типа правильный тип:

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

Это позволит это работать:

classFactory.registerType<Item, bool>("type1");
                              ^^^^^^
                              arg list

Item* item = classFactory.create("type1", true);
Item* item2 = classFactory.create<bool>("type1", 1);

Но это не удастся, так как any занимает bool не int:

Item* item3 = classFactory.create("type1", 1);

@ Ответ Барри более чем полон. Однако, если вы просто заинтересованы в упрощенной фабрике, которая может создавать объекты с конструкторами, принимающими разные параметры, вы можете сделать что-то вроде:

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

Обратите внимание, что я использовал умные указатели здесь, поэтому вам не нужно отслеживать new/deleteбольше

Если все объекты имеют одинаковые типы параметров (здесь bool), просто измените функцию create следующим образом:

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

И изменить createFunc также:

static T* createFunc(bool param)
{
    return new TDerived(param);
}
typedef T* (*PCreateFunc)(bool);

Я сделал это с помощью пакетов параметров C++11:

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

Скомпилируйте этот пример g++ -std=c++11 -o pack pack.cc

Другие вопросы по тегам