为什么std :: type_info是多态的?

时间:2010-10-08 11:32:35

标签: c++ polymorphism rtti typeinfo

是否有理由指出std::type_info是多态的?析构函数被指定为虚拟(并且在“C ++的设计和演变”中对“因此它是多态的”效果进行了评论)。我真的没有看到令人信服的理由。我没有任何具体的用例,我只是想知道它背后是否有任何理由或故事。


以下是我提出并拒绝的一些想法:

  1. 这是一个可扩展点 - 实现可能会定义子类,然后程序可能会尝试将dynamic_cast std::type_info转换为另一个实现定义的派生类型。这可能是原因,但似乎实现添加实现定义的成员(可能是虚拟的)也很容易。希望测试这些扩展的程序无论如何都必然是不可移植的。
  2. 确保在delete基指针时正确销毁派生类型。但是没有标准的派生类型,用户无法定义有用的派生类型,因为type_info没有标准的公共构造函数,因此delete type_info指针永远不会合法且可移植。并且派生类型没有用,因为它们无法构造 - 我知道这种不可构造的派生类型的唯一用途是在is_polymorphic类型特征之类的实现中。
  3. 它保留了具有自定义类型的元类的可能性 - 每个真正的多态class A将获得派生的“元类”A__type_info,它派生自type_info。也许这样的派生类可以以类型安全的方式公开使用各种构造函数参数调用new A的成员,以及类似的东西。但实际上使type_info多态实际上使得这个想法基本上无法实现,因为你必须为你的元类设置元类,无限制,如果所有type_info对象都有静态存储,这就是一个问题持续时间。也许禁止这是使其具有多态性的原因。
  4. 有一些用途可以将RTTI功能(dynamic_cast除外)应用于std::type_info本身,或者有人认为它很可爱,或者如果type_info不是多态的话会让人感到尴尬。但鉴于没有标准派生类型,并且标准层次结构中没有其他类可以合理地尝试交叉投射,问题是:什么?是否可以使用typeid(std::type_info) == typeid(typeid(A))
  5. 等表达式
  6. 这是因为实施者会创建自己的私有派生类型(我相信GCC会这样做)。但是,为什么还要指定它呢?即使析构函数没有被指定为虚拟,并且实现者决定它应该是,但是实现可以将它声明为虚拟,因为它不会更改type_info上的允许操作集,因此便携式程序不会不能分辨出来。
  7. 这与部分兼容的ABI共存的编译器有关,可能是动态链接的结果。如果保证type_info是虚拟的,那么实施者可以以可移植的方式识别他们自己的type_info子类(而不是来自其他供应商的子类)。
  8. 最后一个对我来说是最合理的,但它很弱。

4 个答案:

答案 0 :(得分:9)

C ++标准说typeid返回type_info类型的对象,或者它的一个IMPLEMENTATION-DEFINED子类。所以...我想这几乎就是答案。所以我不明白为什么你拒绝你的第1点和第2点。

第5.2.8段当前C ++标准的第1条内容如下:

  

typeid表达式的结果是   静态类型const的左值   std :: type_info(18.5.1)和动态   输入const std :: type_info或const   名称所在的名称   实现定义的类派生   来自保留的std :: type_info   18.5.1.61中描述的行为   所指对象的生命周期   由左值延伸到结尾   该程序。是否   为type_info调用析构函数   程序结束时的对象是   未指定的。

这反过来意味着可以编写以下代码是合法和正常的: const type_info& x = typeid(expr);可能要求type_info是多态的

答案 1 :(得分:9)

我认为这是为了方便实施者。它允许它们定义扩展的type_info类,并在程序出口处通过指向type_info的指针删除它们,而不必构建特殊的编译器魔法来调用正确的析构函数,或以其他方式跳过箍。

  

肯定可以实施   声明它是虚拟的,因为它没有   更改允许的操作集   在type_info上,所以是一个可移植的程序   无法告诉   差。

我认为这不是真的。请考虑以下事项:

#include <typeinfo>

struct A {
    int x;
};

struct B {
    int x;
};

int main() {
    const A *a1 = dynamic_cast<const A*>(&typeid(int));
    B b;
    const A *a2 = dynamic_cast<const A*>(&b);
}

是否合理,允许第一次动态转换(并且计算为空指针),而不允许第二次动态转换。因此,如果标准中定义type_info以获得默认的非虚拟析构函数,但实现添加了虚拟析构函数,则可移植程序可以区分[*]。

将虚拟析构函数放入标准中似乎比我更简单:

a)在标准中加上一个注释,虽然类定义暗示type_info没有虚函数,但允许它有虚拟析构函数。

b)确定可以区分type_info是否具有多态性的程序集,并禁止所有程序。它们可能不是非常有用或高效的程序,我不知道,但是为了禁止它们,你必须提出一些标准语言来描述你对正常规则所做的具体例外。

因此,我认为标准必须要求虚拟析构函数,要么禁止它。选择它是太复杂了(或许我应该说,我认为它会被判断为不必要的复杂。复杂性从未阻止标准委员会在其认为值得的领域......)

如果它被禁止,那么实施可以:

  • 将虚拟析构函数添加到某个派生类type_info
  • 那个
  • 派生所有typeinfo对象
  • 在内部使用它作为所有内容的多态基类

这将解决我在帖子顶部描述的情况, typeid表达式的静态类型仍然是const std::type_info,因此很难用于定义扩展的实现,其中程序可以dynamic_cast到各种目标,以查看它们在特定情况下具有哪种type_info对象。也许标准希望允许这样做,尽管实现总是可以提供具有不同静态类型的typeid的变体,或者保证某个扩展类的static_cast将起作用,然后让程序运行dynamic_cast从那里开始。

总之,据我所知,虚拟析构函数对实现者来说可能是有用的,删除它不会获得任何除此之外的任何东西我们不会花时间想知道它为什么存在; - )

[*]实际上,我没有证明这一点。我已经证明,在其他条件相同的情况下,非法程序会编译。但是,通过确保所有不相等并且不能编译,实现可能可以解决这个问题。 Boost的is_polymorphic不可移植,所以虽然程序可以测试一个类多态的,但应该是这样的,一致的程序可能无法测试一个class 不是多态的,不应该是。我认为即使这是不可能的,但要证明,为了从标准中删除一行,需要付出相当大的努力。

答案 2 :(得分:2)

关于C ++中最简单的“全局”id是一个类名,typeinfo提供了一种比较这些id的相等性的方法。但是设计是如此笨拙且有限,以至于您需要将typeinfo包装在某个包装类中,例如能够将实例放入集合中。 Andrei Alexandrescu在他的“现代C ++设计”中做到了这一点,我认为typeinfo包装器是Loki库的一部分; Boost中可能还有一个;并且很容易卷起自己的,例如见my own wrapper

但即使对于这样的包装器,typeinfo中通常也不需要虚拟析构函数。

因此问题不在于“嗯,为什么会有一个虚拟的析构函数”而是,正如我所看到的那样,“嗯,为什么设计如此落后,笨拙且无法直接使用”?我将其归结为标准化过程。例如,iostreams也不是精湛设计的例子;不是要模仿的东西。

答案 3 :(得分:2)

  

3 /它保留了具有自定义类型的元类的可能性 - 每个真正的多态class A将得到派生的“元类”A__type_info,它派生自type_info。也许这样的派生类可以以类型安全的方式公开使用各种构造函数参数调用new A的成员,以及类似的东西。但实际上使type_info多态实际上使得这个想法基本上无法实现,因为你必须为你的元类设置元类,无限制,如果所有type_info对象都有静态存储,这就是一个问题持续时间。也许禁止这是使其具有多态性的原因。

聪明......

无论如何,我不同意这种推理:这样的实现很容易排除从type_info派生的类型的元类,包括type_info本身。