为什么 std::type_info 是多态的?
std::type_info
被指定为多态是否有原因?析构函数被指定为虚拟的(并且在《C++ 的设计与演化》中对“因此它是多态的”的效果有注释)。我实在看不出有什么令人信服的理由。我没有任何具体的用例,我只是想知道它背后是否有一个基本原理或故事。
以下是我提出并拒绝的一些想法:
- 这是一个扩展点 - 实现可能会定义子类,然后程序可能会尝试
dynamic_cast
一个std::type_info
来另一种是实现定义的派生类型。这可能是原因,但似乎实现添加实现定义的成员也很容易,该成员可能是虚拟的。无论如何,希望测试这些扩展的程序必然是不可移植的。 - 这是为了确保在
删除
基指针时正确销毁派生类型。但没有标准的派生类型,用户无法定义有用的派生类型,因为type_info
没有标准的公共构造函数,因此删除
一个type_info 指针从来都不是合法且可移植的。派生类型没有用,因为它们无法构造 - 我知道这种不可构造的派生类型的唯一用途是实现 is_polymorphic 类型特征。
- 它为具有自定义类型的元类留下了可能性 - 每个真正的多态
class A
都会获得一个派生的“元类”A__type_info
,它派生自type_info
。也许这样的派生类可以公开以类型安全的方式使用各种构造函数参数调用 new A 的成员,等等。但是,使type_info
本身多态实际上使得这样的想法基本上不可能实现,因为您必须为元类拥有元类,无穷无尽,如果所有type_info
都会出现问题。 code> 对象具有静态存储持续时间。也许排除这一点就是使其成为多态的原因。 - 将 RTTI 功能(
dynamic_cast
除外)应用于std::type_info
本身有一定用途,或者有人认为它很可爱,或者如果type_info
则感到尴尬code> 不是多态的。但鉴于没有标准的派生类型,并且标准层次结构中没有其他类可以合理地尝试交叉转换,问题是:什么?诸如typeid(std::type_info) == typeid(typeid(A))
这样的表达式是否有用? - 这是因为实现者将创建他们自己的私有派生类型(正如我相信 GCC 所做的那样)。但是,为什么还要指定它呢?即使析构函数没有被指定为虚拟,并且实现者决定它应该是,该实现肯定可以将其声明为虚拟,因为它不会更改
type_info
上允许的操作集,因此可移植程序无法区分。 - 这与部分兼容 ABI 共存的编译器有关,可能是动态链接的结果。如果保证
type_info
是虚拟的,也许实现者可以以可移植的方式识别他们自己的type_info
子类(而不是源自其他供应商的子类)。
最后一个对我来说目前是最合理的,但它相当弱。
Is there a reason why std::type_info
is specified to be polymorphic? The destructor is specified to be virtual (and there's a comment to the effect of "so that it's polymorphic" in The Design and Evolution of C++). I can't really see a compelling reason why. I don't have any specific use case, I was just wondering if there ever was a rationale or story behind it.
Here's some ideas that I've come up with and rejected:
- It's an extensibility point - implementations might define subclasses, and programs might then try to
dynamic_cast
astd::type_info
to another, implementation-defined derived type. This is possibly the reason, but it seems that it's just as easy for implementations to add an implementation-defined member, which could possibly be virtual. Programs wishing to test for these extensions would necessarily be non-portable anyway. - It's to ensure that derived types are destroyed properly when
delete
ing a base pointer. But there are no standard derived types, users can't define useful derived types, becausetype_info
has no standard public constructors, and sodelete
ing atype_info
pointer is never both legal and portable. And the derived types aren't useful because they can't be constructed - the only use I know for such non-constructible derived types is in the implementation of things like theis_polymorphic
type trait. - It leaves open the possibility of metaclasses with customized types - each real polymorphic
class A
would get a derived "metaclass"A__type_info
, which derives fromtype_info
. Perhaps such derived classes could expose members that callnew A
with various constructor arguments in a type-safe way, and things like that. But makingtype_info
polymorphic itself actually makes such an idea basically impossible to implement, because you'd have to have metaclasses for your metaclasses, ad infinitum, which is a problem if all thetype_info
objects have static storage duration. Maybe barring this is the reason for making it polymorphic. - There's some use for applying RTTI features (other than
dynamic_cast
) tostd::type_info
itself, or someone thought that it was cute, or embarrassing iftype_info
wasn't polymorphic. But given that there's no standard derived type, and no other classes in the standard hierarchy which one might reasonably try cross-cast to, the question is: what? Is there a use for expressions such astypeid(std::type_info) == typeid(typeid(A))
? - It's because implementers will create their own private derived type (as I believe GCC does). But, why bother specifying it? Even if the destructor wasn't specified as virtual and an implementer decided that it should be, surely that implementation could declare it virtual, because it doesn't change the set of allowed operations on
type_info
, so a portable program wouldn't be able to tell the difference. - It's something to do with compilers with partially compatible ABIs coexisting, possibly as a result of dynamic linking. Perhaps implementers could recognize their own
type_info
subclass (as opposed to one originating from another vendor) in a portable way iftype_info
was guaranteed to be virtual.
The last one is the most plausible to me at the moment, but it's pretty weak.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
我认为它的存在是为了方便实施者。它允许他们定义扩展的 type_info 类,并在程序退出时通过指向 type_info 的指针删除它们,而无需构建特殊的编译器魔术来调用正确的析构函数,或者以其他方式跳圈。
我不认为这是真的。请考虑以下事项:
无论是否合理,第一个动态转换都是允许的(并且计算结果为空指针),而第二个动态转换是不允许的。因此,如果在标准中定义 type_info 具有默认的非虚拟析构函数,但实现添加了虚拟析构函数,则可移植程序可以分辨出差异[*]。
对我来说,将虚拟析构函数放入标准中似乎比以下任一方法更简单:
a) 在标准中添加注释,尽管类定义暗示
type_info
没有虚函数,但允许有一个虚拟析构函数。b) 确定能够区分
type_info
是否多态的程序集,并将其全部禁止。我不知道它们可能不是非常有用或高效的程序,但是要禁止它们,您必须想出一些标准语言来描述您对正常规则所做的特定例外。因此,我认为该标准必须要么强制使用虚拟析构函数,要么禁止它。使其成为可选太复杂了(或者也许我应该说,我认为它会被判断为不必要的复杂。复杂性从未阻止标准委员会在认为值得的领域......)
但是,如果它被禁止,那么实施可以:
type_info
的某个派生类添加一个虚拟析构函数,解决我遇到的情况在帖子顶部描述,但是
typeid
表达式的静态类型仍然是const std::type_info
,所以它是实现很难定义扩展,程序可以dynamic_cast
到各种目标,以查看它们在特定情况下拥有哪种type_info
对象。也许标准希望允许这一点,尽管实现总是可以提供具有不同静态类型的typeid
变体,或者保证特定扩展类的static_cast
能够工作,然后让程序从那里开始dynamic_cast
。总之,据我所知,虚拟析构函数对实现者可能有用,并且删除它不会给任何人带来任何好处,除了我们不会花时间想知道它为什么在那里;-)
[*]实际上,我还没有没有证明这一点。我已经证明,在其他条件相同的情况下,非法程序可以编译。但是,一个实现也许可以通过确保所有内容不相等并且无法编译来解决这个问题。 Boost 的
is_polymorphic
不可移植,因此虽然程序可以测试类是否是多态的,但应该是这样,符合要求的程序可能无法测试一个类不是多态的,这不应该是。我认为,即使这是不可能的,要证明这一点,为了从标准中删除一行,也需要付出很大的努力。I assume it's there for the convenience of implementers. It allows them to define extended
type_info
classes, and delete them through pointers totype_info
at program exit, without having to build in special compiler magic to call the correct destructor, or otherwise jump through hoops.I don't think that's true. Consider the following:
Whether it's reasonable or not, the first dynamic cast is allowed (and evaluates to a null pointer), whereas the second dynamic cast is not allowed. So, if
type_info
was defined in the standard to have the default non-virtual destructor, but an implementation added a virtual destructor, then a portable program could tell the difference[*].Seems simpler to me to put the virtual destructor in the standard, than to either:
a) put a note in the standard that, although the class definition implies that
type_info
has no virtual functions, it is permitted to have a virtual destructor.b) determine the set of programs which can distinguish whether
type_info
is polymorphic or not, and ban them all. They may not be very useful or productive programs, I don't know, but to ban them you have to come up with some standard language that describes the specific exception you're making to the normal rules.Therefore I think that the standard has to either mandate the virtual destructor, or ban it. Making it optional is too complex (or perhaps I should say, I think it would be judged unnecessarily complex. Complexity never stopped the standards committee in areas where it was considered worthwhile...)
If it was banned, though, then an implementation could:
type_info
that would solve the situation I described at the top of the post, but the static type of a
typeid
expression would still beconst std::type_info
, so it would be difficult for implementations to define extensions where programs candynamic_cast
to various targets to see what kind oftype_info
object they have in a particular case. Perhaps the standard hoped to allow that, although an implementation could always offer a variant oftypeid
with a different static type, or guarantee that astatic_cast
to a certain extension class will work, and then let the programdynamic_cast
from there.In summary, as far as I know the virtual destructor is potentially useful to implementers, and removing it doesn't gain anyone anything other than that we wouldn't be spending time wondering why it's there ;-)
[*] Actually, I haven't demonstrated that. I've demonstrated that an illegal program would, all else being equal, compile. But an implementation could perhaps work around that by ensuring that all isn't equal, and that it doesn't compile. Boost's
is_polymorphic
isn't portable, so while it's possible for a program to test that a class is polymorphic, that should be, there may be no way for a conforming program to test that a class isn't polymorphic, that shouldn't be. I think though that even if it's impossible, proving that, in order to remove one line from the standard, is quite a lot of effort.C++ 标准规定
typeid
返回 type_info 类型的对象或其实现定义的子类。所以...我想这就是答案。所以我不明白你为什么拒绝你的第 1 点和第 2 点。当前 C++ 标准第 5.2.8 段第 1 条内容如下:
这反过来意味着可以编写以下代码是合法且良好的:
const type_info& x = typeid(expr);
这可能要求 type_info 是多态的The C++ standard says that
typeid
returns an object of type type_info, OR AN IMPLEMENTATION-DEFINED subclass thereof. So... I guess this is pretty much the answer. So I don't see why you reject your points 1 and 2.Paragraph 5.2.8 Clause 1 of the current C++ standard reads:
Which in turn means that one could write the following code is legal and fine:
const type_info& x = typeid(expr);
which may require that type_info be polymorphic聪明……
无论如何,我不同意这个推理:这样的实现可以轻松排除从
type_info
派生的类型的元类,包括type_info
本身。Clever...
Anyway, I disagree with this reasoning: such implementation could easily rule-out meta classes for types derived from
type_info
, includingtype_info
itself.C++ 中最简单的“全局”id 是类名,
typeinfo
提供了一种比较此类 id 是否相等的方法。但设计是如此尴尬和有限,以至于您需要将typeinfo
包装在某个包装类中,例如,以便能够将实例放入集合中。 Andrei Alexandrescu 在他的“Modern C++ Design”中做到了这一点,我认为 typeinfo 包装器是 Loki 库的一部分; Boost 中可能也有一个;并且很容易自己推出,例如参见 我自己的包装器。但即使对于这样的包装器,通常也不需要
typeinfo
中的虚拟析构函数。因此,问题不在于“呃,为什么会有一个虚拟析构函数”,而在我看来,“呃,为什么设计如此落后、尴尬且不能直接使用”?我将其归因于标准化过程。例如,iostream 也不完全是卓越设计的例子。不是可以效仿的东西。
About the simplest "global" id you can have in C++ is a class name,and
typeinfo
provides a way to compare such id's for equality. But the design is so awkward and limited that you then need to wraptypeinfo
in some wrapper class, e.g. to be able to put instances in collections. Andrei Alexandrescu did that in his "Modern C++ Design" and I think that thattypeinfo
wrapper is part of the Loki library; there's probably one also in Boost; and it's pretty easy to roll your own, e.g. see my own wrapper.But even for such a wrapper there's not in general any need for a virtual destructor in
typeinfo
.The question is therefore not so much "huh, why is there a virtual destructor" but rather, as I see it, "huh, why is the design so backward, awkward and not directly usable"? And I'd put that down to the standardization process. For example, iostreams are not exactly examples of superb design, either; not something to emulate.