0

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

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

我希望能够这样做有几个原因:

  • 扩展的轻松和安全:人们不必修改基础组件系统中的巨大列表来添加组件。
  • 效率:内部查找是使用这个整数值完成的,因此可以是 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();用例:c.getFamilyID() == Foo.getFamilyID(); (而不是实例)
  • 到处都是冗余代码;Component 的每个实现都需要复制粘贴的实现。

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

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

在这件事上的任何帮助或对我为什么要“做错事”(TM)的见解都会很有帮助:-)

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

4

2 回答 2

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();在每个派生类中放入一个静态块。)

您仍然必须(通过合同)要求每个派生类包含一个且仅包含一个 type 的静态成员Registrator。顺便说一句,我认为您无法避免这种情况。

请注意,一般来说,只要您有一个基类和派生类,您就需要依靠契约。例如,如果基类具有虚函数clone(具有通常的语义),则每个派生类都必须实现它。没有办法在编译时强制执行这种事情;某些按合同习惯进行的编程将允许在运行时强制执行对象clone返回的动态类型是正确的,但是即使在运行时,您也无法强制返回的对象是实际副本,而不是一些完全不相关的实例。

我只能说,我在实践中从未发现这是一个问题。任何派生自Component(或任何其他基类)的人都必须知道 Component. 您可以(并且应该)验证某些事情,但最终,您无法验证所有内容,并且在实践中,派生的人会忽略合同,将创建不起作用的代码,您无能为力关于它。(代码审查在这里有很长的路要走。特别是还包括测试覆盖率的代码审查,坚持所有合同问题都经过测试)

编辑:

最后一条评论:我反对使用 anint作为标识符。如果比较标识符的性能很重要,您仍然可以使用char const[]; 如果您保证所有正确获得的标识符都指向char const*相同的字符串(因为您使用的实际标识符将是 a ),您可以只比较指针。派生类的契约是:

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

然后,只需使用char const*返回的classNametype作为您的标识符。

这对于派生类的作者来说要输入更多,但至少在 C++ 中,总是有宏来简化它。事实上,即使使用上面的原始解决方案,我也会为这类事情推荐一个宏。如果派生类都使用宏,您可以更改策略而无需更改任何其他内容。

于 2013-01-05T11:02:54.483 回答
0

在 C++ 中,您可以使用奇怪重复的模板模式来避免代码重复。

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(...)如果您正确地从模板派生,它将在运行时自动检查。所以你会发现像这样的错误

class MySpecificComponent : MyGeneralComponent 
{
};

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

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

于 2013-01-05T11:08:40.977 回答