一个共同基类的派生类的TypeID

6

我正在尝试在C++中实现一些机制,使得所有从共同基类派生的类都被分配一个唯一的“类ID”。例如:

class BaseClass  
{  
    //...
    public: unsigned int GetID( void );
    //...
};
class DerivedClass : public BaseClass
{
}

DerivedClass类和其他所有继承自BaseClass的子类应该能够在不增加任何代码到DerivedClass中的情况下返回唯一标识符。但是,对我来说,C++使这变得相当困难。如果有任何想法,将不胜感激。

提前致谢! ---Dan


ID应该是int类型还是可以是任何唯一的类型,例如typeid返回的类型。你的BaseClass是多态的吗? - Chubsdad
我的基类是多态的,但由于我无法找到一个扎实而全面的解释来说明伴随RTTI的开销的确切性质(包括性能和内存方面),因此我试图完全避免使用它。 - Dan
4个回答

2
你应该听 Alf 的话 :) 这是我的分析:在纯粹的世界中,实现的识别会威胁到虚函数多态性,不能被要求,也不应该需要。在真正编程的肮脏世界中,你可能有一些原因需要唯一标识,例如将数据编组到磁盘、标识诊断信息、跟踪控制流程、累积使用统计信息等等。
如果你的想法是纯洁的,那么你的设计是错误的。离开并弄清楚为什么你不能要求一个唯一的 id。如果你的想法被现实扭曲了,那么你已经愿意为了满足你的要求支付性能和内存的代价,根据规定使用内置语言特性的成本是值得支付的,因为这几乎是实现提供非侵入式标识服务的唯一方式。所谓的非侵入式是指你不需要为每个派生类添加任何内容。显然必须添加一些内容,因此如果你不愿意这样做,就只能接受编译器为你添加的部分。
这里的主要问题是,如果你正在使用动态加载的共享库 (DLLS),运行时类型信息 (RTTI) 可能无法按预期工作。这不仅会对 typeid 产生不利影响,还可能防止捕获你希望捕获的异常 (我曾经受到过伤害!)。需要注意一些细节,以确保虚表和其他 RTTI 是唯一创建的。这可能意味着,例如,如果虚表钩子在你的析构函数上,它就不是内联的,因为在那种情况下,它可能在多个位置生成,破坏了唯一性。在没有 ISO 标准化支持动态加载的情况下,可能需要进行一些调试。

你似乎对 RTTI 的内部工作原理了解得相当多。你知道有没有任何在线资源提供详细描述编译器在实现该功能时使用的机制? - Dan
我手头没有链接,但可以查看gcc开发网站和行业ABI规范。这些规范最初由AMD制定,现在由一个行业组织管理。它基本上概述了用于x86_64处理器的API,包括寄存器中的参数传递、异常处理、动态链接等等,用于各种C++功能。 - Yttrill

1

您没有表明您是否熟悉 typeiddynamic_cast

很有可能它们可以解决您的问题。

如果不能,请描述原因。

祝好!


抱歉,忘了提到:如果可能的话,我想避免使用RTTI,因为我听说它会带来多少开销存在不同的故事,并且不想冒险,因为性能对我的应用程序至关重要。 - Dan
2
@丹:每当你认为自己比编译器更厉害的时候,三思而行。 - Cheers and hth. - Alf
1
@Dan:当你说你不想冒险,因为性能很关键时,你是认真的吗?你连使用typeid进行测试都不能吗?如果你做一个测试运行,它比你希望的要慢一些,谁会死呢? - Steve Jessop
2
@Chubsdad:好的,但是我认为他更倾向于依赖观点而不是实际表现,这有点令人怀疑。 - Steve Jessop
摩根·弗里曼会死。我无法容忍这种想法。但说真的,我宁愿不在开发的早期依赖于RTTI,以免在后期发现它会影响我的性能表现。更好的方法是,我想要理解底层机制是如何工作的,这是我一直很难掌握的。 - Dan
显示剩余2条评论

1
正如Alf所说,这是不必要的。 typeid 已经提供了一个独特的类标识符,尽管这个标识符不是整数。只是为了好玩,如果我被允许放宽“公共基类”条件,那么:
inline unsigned int counter() {
    static unsigned int count = 0;
    return ++count;
}

struct BaseClass {
    virtual unsigned int GetID() = 0;
    virtual ~BaseClass() {}
};

template <typename D>
struct IntermediateClass : BaseClass {
    virtual unsigned int GetID() {
        static unsigned int thisid = counter();
        return thisid;
    }
};

// usage
struct Derived : IntermediateClass<Derived> {
    ...
};

如果要在多线程程序中使用counter,则需要添加线程安全性。

显然,ID仅在程序的给定运行中是唯一的。

如果您的继承层次结构很深,并且有许多具有不同签名的构造函数用于不同的类,则需要在每个派生类和其直接基类之间插入IntermediateClass。但是,您始终可以按如下方式退出所有这些:

inline unsigned int counter() {
    static unsigned int count = 0;
    return ++count;
}

struct BaseClass {
    virtual unsigned int GetID() = 0;
    virtual ~BaseClass() {}
};

template <typename D>
unsigned int ThisID(const D *) {
    static unsigned int thisid = counter();
    return thisid;
}

// usage
struct Derived : BaseClass {
    // this single line pasted in each derived class
    virtual unsigned int GetID() { return ThisID(this); }
    ...
};

我猜这里有一个新语言特性的“机会”:在基类中定义一个虚函数,它是一个带有一个“typename”模板参数的模板函数,并且在每个派生类中自动重写,使用该派生类作为模板参数。由于虚拟模板函数是非法的,因此这是想象中的语法:

struct BaseClass {
    template <typename Derived>
    virtual unsigned int GetID() {
        static unsigned int thisid = counter();
        return thisid;
    }
    virtual ~BaseClass() {}
};

很难仅基于想要重新实现RTTI来证明一种语言特性的合理性,这点需要注意...


这个方法可以正常工作,但是我失去了类的多态性,因为这两个派生类现在继承自两个不同的类。 - Dan
@Steve Jessop:如果确实需要修改Derived类,那么我的方式不是更简单吗? - Chubsdad
@Chubsdad:我不这么认为。你的方法需要在源代码中输入唯一ID(我假设 - 你没有展示函数实现),因此你需要以某种方式分配一个并集中管理它。使用64位类型,管理可以是“程序员随机生成一个”,这还不错。但是,在只有1层深度的情况下,我的方法确实非常简单。 - Steve Jessop
好的,紧张情绪已经过去了,我已经将它恢复到每个派生类中相同的一行。 - Steve Jessop
@Steve Jessop:通过使用类名并从每个派生类中计算出唯一的ID,可以避免任何中央管理。 - Chubsdad
显示剩余5条评论

0

这确实需要修改派生类,但如果缺乏对RTTI的信心是避免使用typeid、dynamic_cast等已经成熟的路线的唯一原因,那么

像这样的东西应该是一个不错的选择。它也通过RTTI返回'int'而不是'type_info'。

class BaseClass{   
    //... 
    public:
       virtual unsigned int GetID( void ); 
    //... 
};

class DerivedClass : public BaseClass{
public: 
   virtual unsigned int GetID( void );
};

我试图避免为每个DerivedClass编写单独的实现;这就是我的问题。如果我没有表达清楚,对不起。 - Dan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接