【问题标题】:TypeID for derived classes of a common base class通用基类的派生类的 TypeID
【发布时间】:2010-11-26 01:59:16
【问题描述】:

我正在尝试在 C++ 中实现某种机制,从而为从公共基类派生的所有类分配一个唯一的“类 ID”。例如:

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

类 DerivedClass 和 BaseClass 的所有其他子级应该能够返回唯一标识符,而无需向 DerivedClass 添加任何额外代码……然而,C++ 让这对我来说相当困难。任何想法将不胜感激。

提前致谢! ---丹

【问题讨论】:

  • ID 应该是 int 还是可以是任何唯一类型,例如typeid 返回的那个。你的 BaseClass 是多态的吗?
  • 我的基类是多态的,但由于我缺乏对伴随 RTTI 的开销的确切性质(性能和内存方面)找到可靠而彻底的解释的能力,我我试图完全避免它。

标签: c++ types typeid


【解决方案1】:

您并未表明您熟悉typeiddynamic_cast

他们可能会解决您的问题。

如果不是,请说明原因。

干杯,

【讨论】:

  • 抱歉,忘了提:尽可能避免使用 RTTI,因为我听说过关于它需要多少开销并且不想冒险的不同故事,因为性能对我的应用程序。
  • @Chubstad:不,这不是评论。 typeid 是您需要类型 ID 时的一般答案。你甚至可以从名字中推断出来。
  • @Dan:每当你认为你可以比编译器做得更好时,请三思。
  • @Dan:当你说你不想冒险,因为性能很关键,你是认真的意思是你甚至不能使用typeid进行测试运行吗?如果您进行试运行,谁会死,而且速度比您希望的要慢?
  • @Chubsdad:好的,但我认为他对实际表现的依赖意见有点可疑。
【解决方案2】:

你应该听听 Alf :) 下面是我的分析:在纯粹的世界中,实现的识别威胁虚函数多态性,不能要求也不应该被要求。

在真实编程的肮脏世界中,您可能有一些唯一标识的原因,例如将数据编组到磁盘、识别诊断消息、跟踪控制流、累积使用统计信息等。

如果你的想法是纯粹的,那么你的设计就是错误的。走开,弄清楚为什么你不能要求一个唯一的 id。

如果你的想法被现实腐蚀了,那么你已经愿意为性能和内存付出代价来满足你的要求,然后按照规范,使用内置语言特性的成本是值得付出的,因为它是几乎是实现您提供非侵入式识别服务目标的唯一方法。非侵入性是指您不需要向每个派生类添加任何内容。显然必须添加一些东西,所以如果您不愿意这样做,您别无选择,只能接受编译器为您添加的内容。

这里的主要问题是,如果您使用的是动态加载的共享库 (DLLS),RTTI 可能无法按预期工作。这不仅会对 typeid 产生不利影响,它还可以防止捕获您希望被捕获的异常(我被咬了!)。可能需要注意确保 vtable 和其他 RTTI 是唯一创建的。这可能意味着,例如,如果 vtables 挂钩在您的析构函数上,它不是内联的,因为在这种情况下它可能会在多个地方生成,从而破坏唯一性。在没有动态加载的 ISO 标准化支持的情况下,这里可能需要进行一些黑客攻击。

【讨论】:

  • 您似乎对 RTTI 的内部工作原理相当了解。您所知道的网上是否有任何地方对编译器级别用于实现该功能的机制进行了详尽的描述?
  • 我手边没有链接,但可以看的地方当然是 gcc 开发站点和行业 ABI 规范,最初由 AMD 制作,但现在由一个行业组织管理。这基本上列出了用于各种 C++ 功能的 x86_64 处理器的 API,包括在寄存器中传递参数、异常处理、动态链接等。
【解决方案3】:

正如 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:自从你说你的类是多态的以来,我一直在犹豫。在第一个选项中,一切都是从 BaseClass 间接派生的,因此您仍然可以通过 BaseClass* 使用 Derived 对象。在第二个选项中,所有内容都直接从 BaseClass 派生,这使得管理构造函数更加容易,但您必须为每个派生类添加一个(相同的)函数。
  • 老鼠,刚刚意识到每个派生类中的行并不相同 - 在这些派生类之一扩展另一个派生类并因此继承两个GetIDImpl 函数。我会改变那个选项。
  • @Steve Jessop:如果 Derived 类确实需要修改,我的方法不是更简单吗?
  • @Chubsdad:我不这么认为。您的方式需要在源中输入唯一 ID(我假设 - 您没有显示功能实现),因此您需要以某种方式分配一个并集中管理它。对于 64 位类型,管理可能只是“程序员随机生成一个”,这还不错。但可以肯定的是,我的方法只有在 1 深层次结构的情况下才真正简单。
【解决方案4】:

这确实需要修改派生类,但如果对 RTTI 缺乏信心是避免完善的 typeid、dynamic_cast 路由的唯一原因,那么

这样的事情应该是一个不错的选择。与 RTTI 的 'type_info' 相比,它还返回一个 'int'。

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

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

【讨论】:

  • 我试图避免为每个 DerivedClass 单独实现;那是我的问题。对不起,如果我没有说清楚。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-25
  • 1970-01-01
  • 2017-06-21
  • 2019-04-05
  • 2017-07-04
  • 1970-01-01
相关资源
最近更新 更多