Выполнить класс от имени?
Представьте, что у меня есть куча связанных с С++ классов (все они расширяют один и тот же базовый класс и предоставляют один и тот же конструктор), которые я объявлял в общем заголовочном файле (который я включаю), и их реализации в некоторых других файлах (которые я компилирую и связывать статически как часть сборки моей программы).
Я хотел бы иметь возможность создать экземпляр одного из них, передающего имя, которое является параметром, который должен быть передан моей программе (либо в виде командной строки, либо как макрос компиляции).
Единственное возможное решение, которое я вижу, - использовать макрос:
#ifndef CLASS_NAME
#define CLASS_NAME MyDefaultClassToUse
#endif
BaseClass* o = new CLASS_NAME(param1, param2, ..);
Это единственный ценный подход?
Ответы
Ответ 1
Это проблема, которая обычно решается с помощью Шаблон реестра:
Это та ситуация, что Шаблон реестра описывает:
Объекты должны контактировать с другим объект, зная только имя объекта или имя службы предоставляет, но не как связаться с ним. Предоставить услугу, которая берет имя объекта, услуги или роли и возвращает удаленный прокси-сервер, который инкапсулирует знания о том, как обратитесь к названному объекту.
Его та же основная модель публикации/поиска которые составляют основу Службы Ориентированной архитектуры (SOA) и для уровень обслуживания в OSGi.
Реализуйте реестр, как правило, с помощью объекта singleton, одинарный объект сообщается во время компиляции или во время запуска имена объектов и способ их создания. Затем вы можете использовать его для создания объекта по требованию.
Например:
template<class T>
class Registry
{
typedef boost::function0<T *> Creator;
typedef std::map<std::string, Creator> Creators;
Creators _creators;
public:
void register(const std::string &className, const Creator &creator);
T *create(const std::string &className);
}
Вы регистрируете имена объектов и функции создания следующим образом:
Registry<I> registry;
registry.register("MyClass", &MyClass::Creator);
std::auto_ptr<T> myT(registry.create("MyClass"));
Мы могли бы затем упростить это с помощью умных макросов, чтобы это можно было сделать во время компиляции. ATL использует шаблон реестра для CoClasses, который может быть создан во время выполнения по имени - регистрация так же проста, как использовать что-то вроде следующего кода:
OBJECT_ENTRY_AUTO(someClassID, SomeClassName);
Этот макрос помещается в ваш файл заголовка где-то, волшебство заставляет его регистрироваться в singleton во время запуска COM-сервера.
Ответ 2
Способ реализации этого заключается в жестком кодировании сопоставления из имен классов с функцией factory. Шаблоны могут сделать код короче. STL может упростить кодирование.
#include "BaseObject.h"
#include "CommonClasses.h"
template< typename T > BaseObject* fCreate( int param1, bool param2 ) {
return new T( param1, param2 );
}
typedef BaseObject* (*tConstructor)( int param1, bool param2 );
struct Mapping { string classname; tConstructor constructor;
pair<string,tConstructor> makepair()const {
return make_pair( classname, constructor );
}
} mapping[] =
{ { "class1", &fCreate<Class1> }
, { "class2", &fCreate<Class2> }
// , ...
};
map< string, constructor > constructors;
transform( mapping, mapping+_countof(mapping),
inserter( constructors, constructors.begin() ),
mem_fun_ref( &Mapping::makepair ) );
EDIT - по общему запросу:) немного переделать, чтобы сделать вещи более гладкими (кредиты Stone Free, которые, вероятно, не захотели добавить сам ответ)
typedef BaseObject* (*tConstructor)( int param1, bool param2 );
struct Mapping {
string classname;
tConstructor constructor;
operator pair<string,tConstructor> () const {
return make_pair( classname, constructor );
}
} mapping[] =
{ { "class1", &fCreate<Class1> }
, { "class2", &fCreate<Class2> }
// , ...
};
static const map< string, constructor > constructors(
begin(mapping), end(mapping) ); // added a flavor of C++0x, too.
Ответ 3
Почему бы не использовать объект factory?
В простейшей форме:
BaseClass* myFactory(std::string const& classname, params...)
{
if(classname == "Class1"){
return new Class1(params...);
}else if(...){
return new ...;
}else{
//Throw or return null
}
return NULL;
}
Ответ 4
В С++ это решение должно быть принято во время компиляции.
Во время компиляции вы можете использовать typedef, а не macor:
typedef DefaultClass MyDefaultClassToUse;
это эквивалентно и избегает макроса (макросы bad; -)).
Если решение должно быть принято во время выполнения, вам необходимо написать свой собственный код для его поддержки. Решение simples - это функция, которая проверяет строку и создает экземпляр соответствующего класса.
Расширенная версия этого (позволяющая независимым разделам кода регистрировать свои классы) была бы map<name, factory function pointer>
.
Ответ 5
Вы упомянули две возможности: макрос командной строки и компиляции, но решение для каждого из них сильно отличается.
Если выбор сделан макросом компиляции, чем простая задача, которая может быть решена с помощью #defines и #ifdefs и т.п. Решение, которое вы предлагаете, так же хорошо, как и любое.
Но если выбор выполняется во время выполнения с использованием аргумента командной строки, вам нужно иметь некоторую фреймворк Factory, который может получить строку и создать соответствующий объект. Это можно сделать с помощью простой статической цепочки if().. else if()... else if()...
, которая имеет все возможности или может быть полностью динамической структурой, где объекты регистрируются и клонируются для предоставления новых экземпляров.
Ответ 6
Хотя вопрос существует уже более четырех лет, он по-прежнему полезен. Поскольку при вызове нового кода, неизвестного на момент компиляции и связывания основных файлов кода, в наши дни очень распространенный сценарий. Одно решение этого вопроса вообще не упоминается. Таким образом, мне нравится указывать аудитории на другое решение, не встроенное в С++. Сам С++ не может вести себя как Class.forName()
, известный из Java, или как Activator.CreateInstance(type)
, известный из .NET. Из-за упомянутых причин, что VM не контролирует JIT-код на лету. Но так или иначе, LLVM, низкоуровневая виртуальная машина, предоставляет необходимые инструменты и библиотеки для чтения в скомпилированном lib. В принципе, вам нужно выполнить два шага:
- скомпилируйте исходный код C/С++, который вы хотите динамически создавать. Вам нужно скомпилировать его в биткод, чтобы вы попали, скажем, в foo.bc. Вы можете сделать это с помощью clang и предоставить компилятор:
clang -emit-llvm -o foo.bc -c foo.c
- Затем вам нужно использовать метод
ParseIRFile()
из llvm/IRReader/IRReader.h
для разбора файла foo.bc
для получения соответствующих функций (сам LLVM знает только функции, поскольку биткод является прямой абстракцией кодов операций процессора и совершенно незнакомым для большего количества высокоуровневые промежуточные представления, такие как байт-код Java). Обратитесь к этой статье за более полным описанием кода.
После настройки этих шагов, описанных выше, вы можете динамически вызывать также из С++ других ранее неизвестных функций и методов.
Ответ 7
В прошлом я реализовал шаблон Factory таким образом, что классы могут самостоятельно регистрироваться во время выполнения без самой Factory, чтобы знать о них конкретно. Ключ заключается в использовании нестандартной функции компилятора (IIRC) "attachment by initialisation", в которой вы объявляете фиктивную статическую переменную в файле реализации для каждого класса (например, bool) и инициализируете ее вызовом регистрации рутина.
В этой схеме каждый класс должен # включать заголовок, содержащий его factory, но Factory ничего не знает, кроме класса интерфейса. Вы можете буквально добавлять или удалять классы реализации из своей сборки и перекомпилировать без изменений кода.
Ловушка заключается в том, что только некоторые компиляторы поддерживают вложение посредством инициализации. Инициализация IIRC файлов при первом использовании инициализирует переменные области видимости файла (так же, как и функция-локальная статическая работа), что здесь не помогает, поскольку фиктивная переменная никогда не доступна, и Карта Factory всегда будет пустой.
Компиляторы, которые меня интересуют (MSVC и GCC), действительно поддерживают это, поэтому для меня это не проблема. Вам нужно будет решить, подходит ли вам это решение.