Зарегистрировать создателя объекта в фабрике объектов - PullRequest
2 голосов
/ 21 августа 2009

У меня есть удобный шаблон фабрики объектов, который создает объекты по их именам идентификаторов типов. Реализация довольно очевидна: 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 ++), я буду в порядке с этим.

Ответы [ 4 ]

5 голосов
/ 21 августа 2009

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

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 не виртуальным. Если есть риск наследования от этого класса несколько раз, он должен быть виртуальным.

3 голосов
/ 21 августа 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>
2 голосов
/ 21 августа 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
1 голос
/ 21 августа 2009

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

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

...