Создание классов по имени с фабричным рисунком
Предположим, у меня есть список классов A, B, C, ...
которые все наследуют от Base
,
Я получаю имя класса в виде строки от пользователя, и я хочу создать экземпляр нужного класса и вернуть указатель на Base
, Как бы вы это реализовали?
Я подумал об использовании хеш-таблицы с именем класса в качестве ключа и указателем на функцию, которая создает экземпляр нужного класса и возвращает Base *
,
Тем не менее, я думаю, что я мог бы использовать здесь фабричный шаблон и сделать его намного проще, но я просто не совсем хорошо его помню, поэтому я хотел попросить совета.
4 ответа
Вот общий пример реализации фабрики:
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;
};
Это не означает, что это будет лучшим в любых обстоятельствах, но это должно быть первое приближение и более полезное значение по умолчанию, чем ручная реализация типа функции, упомянутой stijn. То, как каждая иерархия должна регистрироваться самостоятельно, не является обязательным для Factory, но вам может понравиться упомянутый метод gf (он прост, понятен и очень полезен, и да, в этом случае это устраняет проблемы, присущие макросам).
Вот простой пример фабрики:
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;
}
Самый быстрый, но очень удобный способ во многих областях, будет что-то вроде
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();
}
Вы также можете посмотреть на реализацию фабрики класса Boost.
- Если есть только несколько производных классов, вы можете использовать список "если, еще".
- Если вы планируете иметь много производных классов, лучше отсортировать процесс регистрации классов (как упоминал Георг), чем использовать список "если, еще".
Вот простой пример использования фабричного метода Boost и регистрации классов:
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"]());
Во-первых, да, это как раз то, для чего предназначен заводской шаблон.
(Кстати, ваша другая идея - возможная реализация фабричного шаблона)
Если вы собираетесь сделать это для большого проекта (если нет, просто ответьте на вопрос stijns), вы можете рассмотреть возможность использования где-нибудь ассоциативного контейнера вместо явного ветвления и, возможно, даже перенести ответственность за регистрацию в классы, чтобы
- избегайте изменений кода в одном дополнительном месте (ваша фабрика)
- и, в свою очередь, избегайте, возможно, очень длительного времени перекомпиляции (для реализаций в заголовке) при добавлении класса
Чтобы добиться удобной регистрации в классах, вы можете использовать что-то вроде этого предложения и добавить указатель на функцию или функтор к записям, которые создают производный класс и возвращают указатель на базу.
Если вы не боитесь макросов, вы можете добавить классы к фабрике, просто добавив крошечный макрос в его объявление.