2009-07-08 4 views
27

Представьте, что у меня есть группа связанных с C++ классов (все они расширяют один и тот же базовый класс и предоставляют один и тот же конструктор), которые я объявлял в общем файле заголовка (который я включаю), и их реализации в некоторых других файлах (которые я компилирую и ссылка статически как часть сборки моей программы).Создавать класс от имени?

Я хотел бы иметь возможность создать экземпляр одного из них, передавая имя, которое является параметром, который должен быть передан моей программе (либо в виде командной строки, либо как макрос компиляции).

Единственным возможным решением я вижу использовать макрос:

#ifndef CLASS_NAME 
#define CLASS_NAME MyDefaultClassToUse 
#endif 

BaseClass* o = new CLASS_NAME(param1, param2, ..); 

ли это только ценный подход?

ответ

39

Это проблема, которая обычно решается с помощью Registry Pattern:

Это ситуация, что реестра шаблон описывает:

Объекты должны связаться с другим объект, зная только имя объекта или имя службы, которое оно предоставляет, но не может с ним связаться. Предоставить услугу, которая принимает имя объекта, услуги или роли и возвращает удаленный прокси-сервер, который инкапсулирует знание того, как связывается с именованным объектом.

Это та же самая основная публикация/найти модель , которая формирует основу Service ориентированной архитектуры (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

В C++ это решение должно быть принято во время компиляции.

Во время компиляции вы можете использовать ЬурейеЕ, а не Macor:

typedef DefaultClass MyDefaultClassToUse; 

это эквивалентно и позволяет избежать макро (макросы плохо ;-)).

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

Расширенная версия этого (позволяющая независимым разделам кода регистрировать свои классы) будет map<name, factory function pointer>.

+1

«Расширенная версия этого (позволяющая независимым разделам кода регистрировать свои классы) будет представлять собой карту <имя, указатель фабричной функции>». было бы возможно, из определения класса, зарегистрировать себя на этой карте, если эта карта находится на фабрике в другом месте? В Java я мог/сделал бы это, используя static {} constructors. На самом деле я не хочу, чтобы каждый новый подкласс, который я пишу, изменял код Factory. – puccio

5

Почему бы не использовать объект?

В своей простейшей форме:

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

Вы упомянули две возможности - макрокоманду командной строки и компиляции, но решение для каждого из них сильно отличается.

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

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

10

Способ реализации этого заключается в жестком кодировании сопоставления из имен классов с заводской функцией. Шаблоны могут сделать код короче. 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. 
+0

Года, вызов «трансформировать» в конце заставляет мои глаза болеть. % -) –

+0

Вы правы. Вы также не можете использовать карту и найти подходящий конструктор с циклом. – xtofl

+0

На самом деле, у вас не хватает необходимых функций в вашем примере выше, чтобы сделать то же самое, но без всякого превращения мусора! Внутри структуры отображения у вас есть служебная функция make_pair. Вы уже мыслите правильные строки. Конструктор карты принимает указатель как аргументы (они на самом деле не типизированы). Вы можете воспользоваться этим. Лучше всего использовать ваши статические данные с помощью встроенных типов, поэтому вы должны изменить два члена строки на const char * s. Затем измените свою функцию make_pair на оператор mapped_type –

0

В прошлом , Я реализовал шаблон Factory таким образом, что классы могут самостоятельно регистрироваться во время выполнения, не имея фабрики, которая сама должна знать о них. Ключ заключается в использовании нестандартной функции компилятора (IIRC) «attachment by initialisation», в которой вы объявляете фиктивную статическую переменную в файле реализации для каждого класса (например, bool) и инициализируете ее вызовом регистрации рутина.

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

Уловка заключается в том, что только некоторые компиляторы поддерживают привязку с помощью инициализации - ИИП другие инициализируют переменные области видимости файла при первом использовании (так же, как и функция-локальная статическая работа), что здесь не помогает, поскольку фиктивная переменная никогда не доступна и фабричная карта всегда будет найден пустой.

Компиляторы, которые меня интересуют (MSVC и GCC), действительно поддерживают это, поэтому для меня это не проблема. Вам придется решить, подходит ли вам это решение.

1

Хотя вопрос существует уже более четырех лет, он по-прежнему полезен.Поскольку при вызове нового кода, неизвестного на момент компиляции и связывания основных файлов кода, в наши дни очень распространенный сценарий. Одно решение этого вопроса вообще не упоминается. Таким образом, мне нравится указывать аудитории на другое решение, не встроенное в C++. Сам C++ не может вести себя как Class.forName(), известный из Java, или как Activator.CreateInstance(type), известный из .NET. Из-за упомянутых причин, что VM не контролирует JIT-код на лету. Но так или иначе, LLVM, низкоуровневая виртуальная машина, дает вам необходимые инструменты и библиотеки для чтения в скомпилированную библиотеку lib. В принципе, вам нужно выполнить два этапа:

  1. Скомпилируйте исходный код C/C++, который вы хотите динамически создавать. Вам нужно скомпилировать его в биткод, чтобы вы попали, скажем, в foo.bc. Вы можете сделать это с лязгом и обеспечить переключатель компилятора: clang -emit-llvm -o foo.bc -c foo.c
  2. Вы должны затем использовать метод ParseIRFile() из llvm/IRReader/IRReader.h разобрать файл foo.bc, чтобы получить соответствующие функции (сам LLVM знает только функции как битовый код является прямой абстракцией CPU opcodes и совершенно не знакомы с более высокоуровневыми промежуточными представлениями, такими как байт-код Java). Обратитесь, например, к этому article за более полное описание кода.

После настройки этих шагов, выполненных выше, вы можете динамически вызывать также из C++ других ранее неизвестных функций и методов.

Смежные вопросы