2

我有方便的对象工厂模板,它通过类型 ID 名称创建对象。实现非常明显: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

4 回答 4

5

所以你想把变量定义放在头文件中吗?有一种可移植的方式:模板类的静态变量。所以我们得到:

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 非虚拟的继承。如果存在多次从该类继承的风险,则它应该是虚拟的。

于 2009-08-21T07:00:40.537 回答
3

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>
于 2009-08-21T06:25:04.617 回答
2

为什么不更改宏,以便 REGISTER_CLASS 只注册类而不声明它?

它允许您在实现接口的同时从另一个类派生,您可以将注册放入您的 .cpp 文件中,该文件仅在一个编译单元中,并且它将最小化头文件依赖关系(您不再需要将对象工厂头文件包含在你所有的公共标题)。

有一个 VC++ 扩展应该导致链接器只选择一个全局变量并丢弃未使用的全局变量。有问题的变量需要是全局可见的(= 没有静态,如果编译器不支持扩展会导致链接器错误):

__declspec( selectany )

用法是这样的:

#define REGISTER_CLASS(className, interfaceName) \
  class className; \
  __declspec( selectany ) \
    RegisterClass<className, interfaceName> regInFactory##className; \
  class className : public interfaceName
于 2009-08-21T05:42:50.210 回答
1

显然,您只需要将类注册器声明移动到 .cpp 文件中。也许它应该是 .cpp 文件,其中包含您向该注册商注册的类的实现。

您实际上并不需要 registrar 类,您只需要它的一个实例。通过将其放入 .cpp 文件中,您可以将其完全隐藏在所有其他来源中,但仍会在启动时初始化一个全局对象。

于 2009-08-21T06:05:15.297 回答