10

我执行了以下代码。

#include <iostream>

class Base
{
public:
    virtual void func()
    {
        std::cout<<"Base func called"<<std::endl;
    }
};

class Derived: public Base
{
public:
    virtual void func()  override
    {
        std::cout<<"Derived func called"<<std::endl;
    }
};

int main()
{
    void (Base::*func_ptr)()=&Base::func; //Yes, the syntax is very beautiful.
    Base* bptr=new Derived();
    (bptr->*func_ptr)();
}

我的预期输出是Base func called. 然而,相反,输出是

Derived func called

这让我感到惊讶,因为我认为func_ptr应该只能看到Base成员(因为我认为它func_ptr不会通过 访问成员函数_vptr,而是函数地址本身。

我想知道,在这种情况下虚拟调度是如何发生的(如何访问虚拟表),以及这种行为在 C++ 标准中定义的位置(我什么都找不到)?

4

2 回答 2

9

参考[expr.call],具体看这里

[如果所选函数是虚函数],则调用其在对象表达式的动态类型中的最终覆盖器;这种调用称为虚函数调用

无论您是通过指针还是通过类成员访问来调用函数都是一样的(ref);为虚函数调用的实际函数最终取决于调用它的对象的实际类型

[class.virtual]下标准中的一些(非规范性)注释说了很多相同的事情:

[注3:虚函数调用的解释取决于调用它的对象的类型(动态类型)

[注 4:[...] 虚函数调用依赖于特定对象来确定调用哪个函数。


如果你想知道虚函数调度是如何发生的,你需要寻找一个具体的实现,因为它不规范的。

(我真的很喜欢这篇文章A basic glance at the virtual table,它展示了使用 C 实现它的一种可能方式)

于 2021-09-01T20:14:08.967 回答
3
  1. 你的理解是不正确的。
  2. 这是一个实现细节。不同的编译器可能会以不同的方式实现它。如有疑问,请查看组件。在这个特定的编译器中,它实际上非常聪明。如果存储在指向成员的指针中的值是偶数,则它是一个直接函数指针(所有函数都位于偶数地址)。如果它是奇数,它是 vtable 中的偏移量,加一。所有的 vtable 偏移量也是偶数,因此要获得实际指针,它会减 1。
于 2021-09-01T20:20:30.147 回答