Зарегистрируйте создателя объекта в фабрике объектов

У меня есть удобный шаблон фабрики объектов, который создает объекты по именам идентификаторов их типов. Реализация довольно очевидна: ObjectFactory содержит карту от std::string до функции создания объекта. Затем все создаваемые объекты должны быть зарегистрированы в этой фабрике.

Для этого я использую следующий макрос:

#define REGISTER_CLASS(className, interfaceName) \
   class className; \
   static RegisterClass<className, interfaceName> regInFactory##className; \
   class className : public interfaceName

где RegisterClass

   template<class T, class I>
   struct RegisterClass
   {
      RegisterClass()
      {
         ObjectFactory<I>::GetInstance().Register<T>();
      }
   };

использование

class IFoo
{
public:
   virtual Do() = 0;
   virtual ~IFoo() {}
}

REGISTER_CLASS(Foo, IFoo)
{
   virtual Do() { /* do something */ }
}

REGISTER_CLASS(Bar, IFoo)
{
   virtual Do() { /* do something else */ }
}

Классы определяются и регистрируются в фабрике одновременно.

Проблема в том, что regInFactory... статические объекты определены в файлах .h, поэтому они будут добавляться к каждой единице перевода. Один и тот же создатель объекта будет зарегистрирован несколько раз, и, что более важно, будет много избыточных объектов со статической продолжительностью хранения.

Есть ли способ сделать такую ​​элегантную регистрацию (не копировать/вставлять имена классов и интерфейсов), но при этом не разбрасывать по земному шару лишние статические объекты?

Если хорошее решение нуждается в некоторых расширениях, специфичных для VC++ (не соответствующих стандарту C++), я соглашусь с этим.


person bocco    schedule 21.08.2009    source источник


Ответы (4)


Итак, вы хотите поместить определения переменных в заголовочный файл? Есть портативный способ: статические переменные классов шаблонов. Итак, мы получаем:

template <typename T, typename I>
struct AutoRegister: public I
{
    // a constructor which use ourRegisterer so that it is instantiated; 
    // problem catched by bocco
    AutoRegister() { &ourRegisterer; } 
private:
    static RegisterClass<T, I> ourRegisterer;
};

template <typename T, typename I>
RegisterClass<T, I> AutoRegister<T, I>::ourRegisterer;

class Foo: AutoRegister<Foo, IFoo>
{
public:
    virtual void Do();         
};

Обратите внимание, что я сохранил наследование IFoo не виртуальным. Если существует риск многократного наследования от этого класса, он должен быть виртуальным.

person AProgrammer    schedule 21.08.2009
comment
Классная идея! Я совсем забыл об этом пути. Но ваша версия не будет работать, поскольку наш регистратор никогда не используется, поэтому он не будет создан. Мы можем добавить конструктор, например AutoRegister() { }, или сделать явную структуру шаблона экземпляра AutoRegister‹Foo, IFoo›;. Пожалуйста, обновите свой ответ, если вам не все равно :-) - person bocco; 21.08.2009

Ответ Cygon, вероятно, то, что вам нужно, но вам также может сойти с рук ленивая регистрация, в зависимости от того, для чего именно нужна регистрация.

Переместите регистрацию в специальный базовый класс и прикрепите его помимо интерфейса в макросе.

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

Вероятно, не скомпилируется, но вы должны понять, что я имел в виду:

template<className, interfaceName>
class RegistratorBase
{
public:
     RegistratorBase()
     {
          static bool registered = false;
          if(!registered)
                ObjectFactory<interfaceName>::GetInstance().Register<className>();
     }
};

#define REGISTER_CLASS(className, interfaceName) \
  class className : public interfaceName, private RegistratorBase<className, interfaceName>
person Eugene    schedule 21.08.2009
comment
Мне очень нравится это решение. Я не думаю, что Марко сейчас даже нужен. - person iain; 21.08.2009

Почему бы не изменить макрос, чтобы REGISTER_CLASS только регистрировал класс, не объявляя его?

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

Существует расширение VC++, которое должно заставить компоновщика выбирать только один из глобальных переменных и отбрасывать неиспользуемые. Рассматриваемая переменная должна быть видна глобально (= не статическая, вызовет ошибки компоновщика, если компилятор не поддерживает расширение):

__declspec( selectany )

Использование будет таким:

#define REGISTER_CLASS(className, interfaceName) \
  class className; \
  __declspec( selectany ) \
    RegisterClass<className, interfaceName> regInFactory##className; \
  class className : public interfaceName
person Cygon    schedule 21.08.2009
comment
Я должен включить заголовок фабрики, так как есть несколько удобных методов, таких как Create() и Clone(), для выполнения реальной работы. Эта фабрика объектов предназначена для иерархии объектов, поэтому я хочу быть уверен, что разработчик не забудет зарегистрировать новый объект при его добавлении в иерархию. Спасибо за избранное! Надеюсь, что мои параметры компоновщика хороши для этого. Пойду копать MSDN :-) - person bocco; 21.08.2009

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

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

person sharptooth    schedule 21.08.2009
comment
Это отделяет декларацию от регистрации, хотя - person Eugene; 21.08.2009
comment
Я не вижу в этом проблемы. - person sharptooth; 21.08.2009
comment
Такой класс должен быть зарегистрирован в этой фабрике. Так что было бы удобнее и безопаснее делать регистрацию одновременно с декларированием. - person bocco; 21.08.2009
comment
Я понял. Однако мы делали то, что я предлагаю, целую вечность, и до сих пор не видели реальных проблем с обслуживанием. - person sharptooth; 21.08.2009
comment
Хорошо, я вижу. Думаю, это просто личный вкус :-) - person bocco; 21.08.2009