Подтвердить что ты не робот

Выполнить класс от имени?

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

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

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

#ifndef CLASS_NAME
#define CLASS_NAME MyDefaultClassToUse
#endif

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

Это единственный ценный подход?

4b9b3361

Ответ 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), действительно поддерживают это, поэтому для меня это не проблема. Вам нужно будет решить, подходит ли вам это решение.