为什么 C++ RTTI 需要虚方法 table?
Why does C++ RTTI require a virtual method table?
为什么 C++ RTTI 要求 class 有一个虚拟方法 table?虽然使用 table 作为多态向上转换的手段似乎是合理的,但从设计的角度来看,它似乎并不是严格要求的。例如,class 可以包含传达信息的散列或唯一标识符。
对于认为这个问题过于琐碎的 C++ 专家来说,这将有助于这个问题的发帖者(他是 C++ 的初学者)解释为什么 vtables 需要从RTTI 的设计观点,以及实现 RTTI 的其他设计方法(而不是使用 vtables)是什么(以及为什么它们 work/don 不如 vtables).
从语言的角度来看,答案是:不是。 C++ 标准中没有任何地方说明虚函数是如何实现的。编译器可以自由地确保调用正确的函数,但是它认为合适。
那么,用 id 和丢弃 vtable? (用 id 替换 vtable 并没有任何帮助,一旦你解决了 vptr,你就已经知道 运行-time 类型)
运行时间如何知道实际调用哪个函数?
考虑:
template <int I>
struct A {
virtual void foo() {}
virtual void bar() {}
virtual ~A() {}
};
template <int I>
struct B : A<I> {
virtual void foo() {}
};
假设您的编译器给出 A<0>
... 让我们称之为 vid ... 0 和 A<1>
vid 1。请注意 A<0>
和 A<1>
在这一点上是完全不相关的 类。如果你说 a0.foo()
其中 a0
是 A<0>
会发生什么?在 运行 时间,非虚拟函数只会导致静态调度 call
。但是对于虚函数来说,调用函数的地址必须在运行时确定。
如果你只有 vid 0,你仍然需要编码你想要的功能。这将导致大量 if-else 分支,以找出正确的函数指针。
if (vid == 0) {
if (fid == 0) {
call A<0>::foo();
} else if (fid == 1) {
call A<0>::bar();
} /* ... */
} else if (vid == 1) {
if (fid == 0) {
call A<1>::foo();
} else if (fid == 1) {
call A<1>::bar();
} /* ... */
} /* ... */
这会失控的。因此,table。在 A<0>
's vtable and you have the address of the actual function to call. If you have a B<0>
object on your hands instead, add the offset to that class' table 的基指针的基址上添加一个标识 foo()
函数的偏移量。
理论上,编译器可以为此生成 if-else 代码,但事实证明,指针加法速度更快,生成的代码更小。
Vtable 是提供虚函数的一种非常有效的方式。对于每个对象单个指针的价格,class 的每个成员都可以共享同一个静态 vtable。
为每个 class 添加第二组静态信息将需要每个对象的第二个指针。使现有的 vtable 指针执行双重任务要容易得多。
最终,一切都取决于历史和权衡。
一方面你需要与 C 兼容,特别是标准布局类型必须与 C 中的布局相同,这意味着 RTTI 没有位置。
另一方面,将 RTTI 添加到 vtable 将不会导致实例的大小成本。
C++ 的设计者决定将这两个事实结合到当前的实现中:只有多态类型才具有动态 RTTI 信息。
您仍然可以获得静态 RTTI 信息并为非多态类型制作自己的布局:
template<typename T>
struct S
{
const std::type_info &type = typeid(T);
T value;
};
你甚至可以将 void 指针传递给值,它们将具有与 T 相同的结构,而且你知道它们背后有一个类型信息指针。
为什么 C++ RTTI 要求 class 有一个虚拟方法 table?虽然使用 table 作为多态向上转换的手段似乎是合理的,但从设计的角度来看,它似乎并不是严格要求的。例如,class 可以包含传达信息的散列或唯一标识符。
对于认为这个问题过于琐碎的 C++ 专家来说,这将有助于这个问题的发帖者(他是 C++ 的初学者)解释为什么 vtables 需要从RTTI 的设计观点,以及实现 RTTI 的其他设计方法(而不是使用 vtables)是什么(以及为什么它们 work/don 不如 vtables).
从语言的角度来看,答案是:不是。 C++ 标准中没有任何地方说明虚函数是如何实现的。编译器可以自由地确保调用正确的函数,但是它认为合适。
那么,用 id 和丢弃 vtable? (用 id 替换 vtable 并没有任何帮助,一旦你解决了 vptr,你就已经知道 运行-time 类型)
运行时间如何知道实际调用哪个函数?
考虑:
template <int I>
struct A {
virtual void foo() {}
virtual void bar() {}
virtual ~A() {}
};
template <int I>
struct B : A<I> {
virtual void foo() {}
};
假设您的编译器给出 A<0>
... 让我们称之为 vid ... 0 和 A<1>
vid 1。请注意 A<0>
和 A<1>
在这一点上是完全不相关的 类。如果你说 a0.foo()
其中 a0
是 A<0>
会发生什么?在 运行 时间,非虚拟函数只会导致静态调度 call
。但是对于虚函数来说,调用函数的地址必须在运行时确定。
如果你只有 vid 0,你仍然需要编码你想要的功能。这将导致大量 if-else 分支,以找出正确的函数指针。
if (vid == 0) {
if (fid == 0) {
call A<0>::foo();
} else if (fid == 1) {
call A<0>::bar();
} /* ... */
} else if (vid == 1) {
if (fid == 0) {
call A<1>::foo();
} else if (fid == 1) {
call A<1>::bar();
} /* ... */
} /* ... */
这会失控的。因此,table。在 A<0>
's vtable and you have the address of the actual function to call. If you have a B<0>
object on your hands instead, add the offset to that class' table 的基指针的基址上添加一个标识 foo()
函数的偏移量。
理论上,编译器可以为此生成 if-else 代码,但事实证明,指针加法速度更快,生成的代码更小。
Vtable 是提供虚函数的一种非常有效的方式。对于每个对象单个指针的价格,class 的每个成员都可以共享同一个静态 vtable。
为每个 class 添加第二组静态信息将需要每个对象的第二个指针。使现有的 vtable 指针执行双重任务要容易得多。
最终,一切都取决于历史和权衡。
一方面你需要与 C 兼容,特别是标准布局类型必须与 C 中的布局相同,这意味着 RTTI 没有位置。
另一方面,将 RTTI 添加到 vtable 将不会导致实例的大小成本。
C++ 的设计者决定将这两个事实结合到当前的实现中:只有多态类型才具有动态 RTTI 信息。
您仍然可以获得静态 RTTI 信息并为非多态类型制作自己的布局:
template<typename T>
struct S
{
const std::type_info &type = typeid(T);
T value;
};
你甚至可以将 void 指针传递给值,它们将具有与 T 相同的结构,而且你知道它们背后有一个类型信息指针。