Создание классов по имени с помощью шаблона factory

Предположим, что у меня есть список классов A, B, C, ..., которые все наследуют от Base.

Я получаю имя класса как строку от пользователя, и я хочу создать экземпляр правильного класса и вернуть указатель на Base. Как вы это реализуете?

Я подумал о том, чтобы использовать хеш-таблицу с именем класса в качестве ключа и указатель функции к функции, создающей правильный класс и возвращающий Base *.

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

Ответ 1

Вот общий factory пример:

template<class Interface, class KeyT=std::string>
struct Factory {
    typedef KeyT Key;
    typedef std::auto_ptr<Interface> Type;
    typedef Type (*Creator)();

    bool define(Key const& key, Creator v) {
        // Define key -> v relationship, return whether this is a new key.
        return _registry.insert(typename Registry::value_type(key, v)).second;
    }
    Type create(Key const& key) {
        typename Registry::const_iterator i = _registry.find(key);
        if (i == _registry.end()) {
            throw std::invalid_argument(std::string(__PRETTY_FUNCTION__) +
                                        ": key not registered");
        }
        else return i->second();
    }

    template<class Base, class Actual>
    static
    std::auto_ptr<Base> create_func() {
        return std::auto_ptr<Base>(new Actual());
    }

private:
    typedef std::map<Key, Creator> Registry;
    Registry _registry;
};

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

Здесь простой пример factory:

struct Base {
    typedef ::Factory<Base> Factory;
    virtual ~Base() {}
    virtual int answer() const = 0;

    static Factory::Type create(Factory::Key const& name) {
        return _factory.create(name);
    }
    template<class Derived>
    static void define(Factory::Key const& name) {
        bool new_key = _factory.define(name,
            &Factory::template create_func<Base, Derived>);
        if (not new_key) {
            throw std::logic_error(std::string(__PRETTY_FUNCTION__) +
                                   ": name already registered");
        }
    }

private:
    static Factory _factory;
};
Base::Factory Base::_factory;

struct A : Base {
    virtual int answer() const { return 42; }
};

int main() {
    Base::define<A>("A");
    assert(Base::create("A")->answer() == 42);
    return 0;
}

Ответ 2

самый быстрый, но очень полезный способ во многих областях, будет что-то вроде

Base* MyFactoryMethod( const std::string& sClass ) const
{
  if( sClass == "A" )
    return CreateNewA();
  else if( sClass == "B" )
    return new CreateClassB();
  //....
  return 0;
}

A* CreateClassA() const
{
  return new A();
}

Ответ 3

Вы также можете изучить версию Boost factory.

  • Если есть только несколько производных классов, вы можете использовать список "if, else".
  • Если вы планируете иметь много производных классов, лучше отсортировать процесс регистрации класса (как упоминалось Georg), чем использовать список "if, else".

Вот простой пример использования метода Boost factory и регистрации класса:

typedef boost::function<Parent*()> factory;

// ...

std::map<std::string, factory> factories;

// Register derived classes
factories["Child1"] = boost::factory<Child1*>();
factories["Child2"] = boost::factory<Child2*>();

// ...

// Instantiate chosen derived class
auto_ptr<Parent> pChild = auto_ptr<Parent>(factories["Child1"]());

Ответ 4

Прежде всего, да, это именно то, для чего предназначен шаблон factory.
(Кстати, ваша другая идея - возможная реализация шаблона factory)

Если вы намереваетесь сделать это для большого проекта (если нет, просто выберите stijns answer), вы можете захотеть использовать ассоциативный контейнер где-нибудь вместо явное ветвление и, возможно, даже перенос ответственности регистрации в классы на

  • избегать изменений кода в одном дополнительном месте (ваш factory)
  • и, в свою очередь, избегать, возможно, очень длительных периодов перекомпиляции (для in-header-реализаций) при добавлении класса

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