给定一个带有虚函数f的基类B,派生类D和它自己的f实现是我的场景:
B& b = *new D; b.f();
D& d = *new D; d.f();
项目符号1中的代码是否涉及从vtable中获取f的地址然后跳转? 清单2中的代码是否涉及任何vtable查找?
我知道这些可能与编译器有关,也许标准不会指定实现细节。在这种情况下,如果了解GCC或CLANG如何处理这些案件的人提供了解释,我将不胜感激。
感谢。
编辑:请剪切粘贴汇编程序输出;我仍然不确定为什么在第二种情况下应该在vtable中进行任何查找。答案 0 :(得分:3)
来自维基:
C ++标准并没有明确规定必须如何实现动态调度,但编译器通常在同一基本模型上使用微小变化。通常,编译器为每个类创建一个单独的vtable。
编译器将为包含virtual
函数的每个类创建一个vtable,如here所述:
每个使用虚函数的类(或者从使用虚函数的类派生)都会被赋予它自己的虚拟表。该表只是编译器在编译时设置的静态数组。虚拟表包含可由类的对象调用的每个虚函数的一个条目。此表中的每个条目只是一个函数指针,指向该类可访问的派生函数最多。
答案 1 :(得分:2)
这是一个虚拟方法调用。在这两种情况下,运行时都必须有一个vtable查找。
编译器无法知道在对构造函数的调用和对函数的调用之间是否发生了某些事情,这可能修改了b和d并将其实际类型更改为其他类型。
标准说:
2如果在类Base和a中声明了虚拟成员函数vf class派生,直接或间接来自Base,一个成员 函数vf具有相同的名称,参数类型列表(8.3.5)和 声明Base :: vf的cv-qualification,然后Derived :: vf也是 虚拟(无论是否如此声明)并覆盖它 基地:: VF。对于 方便我们说任何虚函数都会覆盖自己。然后进去 任何格式正确的类,用于在其中声明的每个虚函数 类或其任何直接或间接基类都有一个独特的 最终覆盖,覆盖该功能和所有其他覆盖 那个功能。成员查找规则(10.2)用于 确定a范围内虚函数的最终覆盖 派生类但忽略using声明引入的名称。
7 [注意:虚拟函数调用的解释取决于 在它被调用的对象的类型(动态类型), 而对非虚拟成员函数的调用的解释 仅取决于表示该指针的指针或引用的类型 对象(静态类型)(5.2.2)。 - 后注]
这并没有强制要求如何完成,而是非常明确地说明在调用时必须在对象的实际类型上解析调用。
答案 2 :(得分:1)
标准方法是使用vtable。只有好的或非常聪明的编译器才能优化它。
查看编译器的汇编结果以了解答案。大部分时间(如果不是所有时间)都有vtable访问权。
答案 3 :(得分:0)
在第一种情况下,您在声明该函数f()
的类的对象上调用virtual
,因此程序必须执行vtable查找才能找到正确的派生类重写{{1} (除非在帖子中的简单示例中允许编译器优化它,知道确切的类)
在第二种情况下,除非f()
将D
声明为虚拟,否则没有vtable查找 - 通过知道类标识f()f
,编译器知道哪个D
在编译时调用。
更新(基于评论):第二种情况等同于第一种情况,因为f()
的覆盖函数也是虚拟的,因为D
声明它是虚拟的(我已经学到了新东西)今天也是:))
答案 4 :(得分:0)
在两种情况下都必须查找VTable,因为在编译时,代码不知道要调用哪个函数。
由于所有指令都是在编译时生成的,因此虚函数调用将转换为v-table查找,然后转换为跳转。