我目前正在尝试创建一个系统,我希望能够在运行时为类分配唯一的系列ID。基本上我希望能够在运行时注册它们之后基于整数值来区分类。
这个用例就是这个系统将被用作组件系统的官僚机构。所有类都是类Component的后代(不一定是直接的);并在运行时注册。
我希望能够做到这一点有几个原因:
我正在寻找一个编译时检查的实现;不是基于合同的解决方案。 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++); }
}
这显然不起作用有两个原因:
我认为我可以使用指定静态变量的模板在C ++中解决该问题,但是当该类不是Component的直接后代时,这将变得无法使用。
我也不能在Java中使用枚举,因为它们是特定于语言的,并且组件的数量会使单个文件的代码变得庞大。 (也;他们都必须在一个地方再次指定)
在这个问题上的任何帮助或对我为什么试图“做错事”(TM)的见解都会非常有帮助: - )
编辑:为了澄清,我想在编译时确保可以在组件类中设置的静态整数的代码约定。
答案 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(...)
将在运行时自动检查。所以你会发现像
class MySpecificComponent : MyGeneralComponent
{
};
在运行时。否则,此派生类将使用与直接库相同的接口实现,并使用相同的静态变量,这将是一个错误。
您可能已经注意到您不需要手动注册任何课程。这是通过在main()函数启动之前动态初始化静态变量来完成的。所以你不需要做任何事情。通过这种方式,您可以轻松地在一个位置实现您的类,而无需更改其他文件,也无需重复代码重复 - open/closed principle卓越。