2

给定一个带有虚函数 f 的基类 B、一个派生类 D 和它自己的 f 实现,这是我的场景:

  1. B& b = *new D; b.f();
  2. D& d = *new D; d.f();

项目符号 1 中的代码是否涉及从 vtable 获取 f 的地址然后跳转?清单 2 中的代码是否涉及任何 vtable 查找?

我知道这些可能取决于编译器,并且标准可能不会指定实现细节。在这种情况下,如果了解 GCC 或 CLANG 如何处理这些情况的人提供解释,我将不胜感激。

谢谢。

编辑:请剪切粘贴您的汇编器输出;我仍然不确定为什么在第二种情况下应该在 vtable 中进行任何查找。

4

5 回答 5

3

来自维基:

C++ 标准并没有明确规定必须如何实现动态调度,但编译器通常在相同的基本模型上使用微小的变化。通常,编译器为每个类创建一个单独的 vtable。

编译器将为每个包含函数的类创建一个 vtable virtual如下所述:

每个使用虚函数的类(或派生自使用虚函数的类)都有自己的虚表。该表只是编译器在编译时设置的静态数组。一个虚拟表包含一个条目,对应于类对象可以调用的每个虚拟函数。此表中的每个条目只是一个函数指针,它指向该类可访问的最衍生函数。

于 2012-05-15T19:20:13.420 回答
2

这是一个虚拟方法调用。在这两种情况下,都必须在运行时进行 vtable 查找。

编译器无法知道在对构造函数的调用和对函数的调用之间是否发生了某些事情,这可能已经修改了 b 和 d 并将它们的实际类型更改为其他内容。

标准是怎么说的:

2 如果在Base类和Derived类中声明了一个虚成员函数vf,直接或间接从Base派生,一个同名的成员函数vf,parameter-type-list (8.3.5),和cv-qualification由于 Base::vf 被声明,那么 Derived::vf 也是虚拟的(无论是否如此声明)并且它覆盖 Base::vf。为方便起见,我们说任何虚函数都会覆盖自身。然后在任何格式良好的类中,对于在该类或其任何直接或间接基类中声明的每个虚函数,都有一个唯一的最终覆盖器覆盖该函数和该函数的所有其他覆盖器。成员查找规则 (10.2) 用于确定派生类范围内的虚函数的最终覆盖器,但忽略使用声明引入的名称。

7 [注:虚函数调用的解释取决于被调用对象的类型(动态类型),而非虚成员函数调用的解释仅取决于对象的类型表示该对象的指针或引用(静态类型)(5.2.2)。——尾注]

这并没有强制要求它是如何完成的,但非常清楚地表明调用必须在调用时根据对象的实际类型来解析。

于 2012-05-15T19:09:13.647 回答
1

标准方法是使用 vtable。只有好的或非常聪明的编译器才会优化它。

查看编译器的汇编结果以了解答案。大多数时候(如果不是一直)有一个 vtable 访问。

于 2012-05-15T19:11:22.847 回答
0

在第一种情况下,您正在调用f()声明该函数的类的对象virtual,因此程序必须执行 vtable 查找以找到正确的派生类的覆盖f()(除非在您帖子中的简单示例中允许编译器对此进行优化,知道确切的类别)

在第二种情况下,没有 vtable 查找,除非也D声明f()f为 virtual ——通过知道类 id D,编译器知道f()在编译时要调用哪个。

更新(基于评论):第二种情况相当于第一种情况,因为D' 的覆盖函数也是虚拟的,因为B它声明它是虚拟的(我今天也学到了一些新东西:))

于 2012-05-15T19:12:28.270 回答
0

在这两种情况下都必须查找 VTable,因为在编译时,代码不知道要调用哪个函数。

由于所有指令都是在编译时生成的,因此虚函数调用将被转换为 v-table 查找然后跳转。

于 2012-05-15T19:14:46.337 回答