创建特定于运行时类的寄存器系统的最佳方法?

时间:2013-01-05 10:34:48

标签: java c++ oop

我目前正在尝试创建一个系统,我希望能够在运行时为类分配唯一的系列ID。基本上我希望能够在运行时注册它们之后基于整数值来区分类。

这个用例就是这个系统将被用作组件系统的官僚机构。所有类都是类Component的后代(不一定是直接的);并在运行时注册。

我希望能够做到这一点有几个原因:

  • Eaze和扩展的安全性:人们不必修改基本组件系统中的巨大列表来添加组件。
  • 效率:内部查找使用此整数值完成,因此可以是O(1)而不是搜索查找。由于这些组件将构成系统的大部分,我宁愿不将其作为实例变量。我不能忽视这方面,因为测试用例已经证明我买不起> O(1)移除和插入。 (第一个实现使用地图查找表)

我正在寻找一个编译时检查的实现;不是基于合同的解决方案。 Java中基于合同的解决方案将是:

interface Component {

   // Should return the value of a static variable
   int getFamilyID();
   int setFamilyID(int id);
}

class Foo implements Component {

   static int familyID = 0;

   int getFamilyID(){ return familyID; }
   int setFamilyID(int id){ familyID = id; }
}

class System {  // Singleton
   static int registeredComponents = 0;
   void register(Component c){ c.setFamilyID(registeredComponents++); }   
}

这显然不起作用有两个原因:

  • 我不能指定A.getFamilyID(),除非我将变量设为public; usecase:c.getFamilyID()== Foo.getFamilyID(); (而不是instanceof)
  • 遍布各处的冗余代码; Component的每个实现都需要具有复制粘贴的实现。

我认为我可以使用指定静态变量的模板在C ++中解决该问题,但是当该类不是Component的直接后代时,这将变得无法使用。

我也不能在Java中使用枚举,因为它们是特定于语言的,并且组件的数量会使单个文件的代码变得庞大。 (也;他们都必须在一个地方再次指定)

在这个问题上的任何帮助或对我为什么试图“做错事”(TM)的见解都会非常有帮助: - )

编辑:为了澄清,我想在编译时确保可以在组件类中设置的静态整数的代码约定。

2 个答案:

答案 0 :(得分:1)

您基本上要求的是特定的运行时 在编译时检查行为。在一般情况下,那 根本不可能:你可以编写你想要的所有功能, 但编译器永远无法确保您调用 为每种类型赋予一次,只有一次的函数。最好的你 可以做的是使用某种静态变量,使功能 私人,并拨打电话到注册 构造:

class Component
{
protected:
    class Registrator
    {
        static int nextId;
        int id;
    public:
        Registrator() ; id( nextId ++ ) {}
        int id() const { return id; }
    };
    //  ...
};

class Derived ; public Component
{
    static Registrator ourId;
    //  ...
};

(您也可以在Java中执行此操作。只需将static Registrator ourId = new Registrator();放在每个派生的静态块中 类。)

您仍然需要(通过合同)每个派生类 包含一个,只有一个Registrator类型的静态成员。 在手边,我认为你不能避免这种情况。

请注意,通常情况下,只要您拥有基类并派生 课程,你需要指望合同。如果是基类 例如,有一个虚函数clone(与通常的一样 语义),每个派生类都必须实现它。有 在编译时无法强制执行此类操作;一些 通过合同惯用法编程将允许强制执行 动态类型的对象clone返回的运行时 正确,但即使在运行时,也无法强制执行 返回的对象是一个实际的副本,而不是一些完全的副本 无关的实例。

我所能说的就是我从来没有发现这是一个 实践中的问题。任何来自Component(或任何 其他基类)必须知道前提的合同 Component。你可以(并且应该)验证一些事情,但是 最后,你不能验证一切,在实践中,有人 谁来源,忽略合同,将创建代码 不起作用,你无能为力。 (码 审查在这里有很长的路要走。特别是代码审查也 包括测试覆盖,坚持所有合同 问题经过测试)

编辑:

最后一条评论:我会反对使用int 标识符。如果比较标识符的性能是 重要的是,您仍然可以使用char const[];如果你保证 所有正确获得的标识符都指向(因为实际的 你使用的标识符是char const*)到同一个字符串, 你可以比较一下指针。衍生合同 那就是:

class Derived : public Component
{
public:
    static char const* className() { return "Derived"; }
    //  overriding virtual function in Component...
    char const* type() const { return className(); }
    //  ...
};

然后,只需使用char const*或{}返回的className即可 type作为您的标识符。

对于要输入的派生类的作者来说,这有点多了, 但至少在C ++中,总有宏来简化它。 事实上,我甚至会为这类事情推荐一个宏 用上面的原始解决方案。如果派生类全部 使用宏,您可以更改策略而无需更改 别的什么。

答案 1 :(得分:0)

在C ++中,您可以使用curiously recurring template pattern以避免代码重复。

class Component 
{
public:
    virtual ~Component() {}
    virtual int getFamilyId() const = 0;
};

// each instance is assigned a unique int at construction
class FamilyId 
{ 
    static int numberOfExistingIds = 0;
    int id;
public:
    FamilyId() : id( numberOfExistingIds++ ) {}
    int getId() const { return id; }
};

// implementation is done only in this template class
template <typename Derived, typename Base = Component> 
class ComponentImpl : public Base 
{
    static FamilyId familyId; // one instance per class for unique id
public:
    virtual int getFamilyId() const 
    { 
        assert( typeid(*this) == typeid(Derived) ); 
        return familyId.getId(); 
    }
};

设置完成后,您可以轻松地在Component类层次结构中创建新类。

// first derived class, automagically implemented by template magic
class MyGeneralComponent 
    : public ComponentImpl<MyGeneralComponent> 
{
     /* add new methods here */ 
};

// class further down in the hierarchy are also possible, 
// by using the second template argument. The implementation still works. 
class MySpecificComponent 
    : public ComponentImpl<MySpecificComponent,MyGeneralComponent> 
{
     /* add new methods here */ 
};

如果您从模板中正确派生,assert(...)将在运行时自动检查。所以你会发现像

这样的bug
class MySpecificComponent : MyGeneralComponent 
{
};

在运行时。否则,此派生类将使用与直接库相同的接口实现,并使用相同的静态变量,这将是一个错误。

您可能已经注意到您不需要手动注册任何课程。这是通过在main()函数启动之前动态初始化静态变量来完成的。所以你不需要做任何事情。通过这种方式,您可以轻松地在一个位置实现您的类,而无需更改其他文件,也无需重复代码重复 - open/closed principle卓越。