C++中Vptr和Vtable的机制

15
在C++中,进行动态绑定时,请考虑以下示例...
class Base
{
  virtual void fun()
  {
     cout<<"Base";
  }      
};

class Derived : public Base
{
   void fun()
   {
     cout<<"Derived";
   }
};

int main()
{
  Base *bptr;
  Derived d;
  bptr=&d;
  bptr->fun();
  return 0;
}

上述C++程序的输出由于virtual关键字/动态绑定的声明是“Derived”。
据我理解,将会创建一个虚拟表(Vtable),其中包含虚函数的地址。在这种情况下,派生类所创建的虚拟表指向了继承的虚拟fun()函数。并且bptr->fun()将被解析为bptr->vptr->fun();。这指向了继承的基类函数本身。我还不完全清楚如何调用派生类函数?

3
请注意,这里是“int main”而不是“void main”,同时类的声明需要以“;”结尾。 - Sebastian Mach
下面的答案看起来不错,但如果你想更深入地了解这个主题,我建议阅读《C++对象模型内幕》(ISBN: 978-0201834543)。 - Sean Cline
3个回答

15

我刚刚阅读了这个链接虚拟表和_vptr

它说工作流程如下..

  1. base_ptr->base_vptr---->用于检查基类中的虚拟函数访问权限。

  2. base_ptr->derived_vptr->virtual_function()---> 调用/调用虚拟函数。

因此,派生类的虚拟函数被调用。希望你会发现这有帮助。


已经参考了这个链接,但是没有提供完整的解决方案。你的第二点说虚函数会被调用,这就是问题所在。但在这种情况下必须调用派生类。 - Blue Diamond
1
@BlueDiamond: base_ptr->derived_vptr->virtual_function()---> 调用派生类中的虚函数。基类指针将包含派生类虚指针(派生类虚指针指向派生类虚表)。然后,当派生类虚指针调用虚函数时,将调用派生类虚函数。 - Santosh Sahu
1
派生的虚函数不应该被调用。派生类的虚函数是从基类继承而来的。重写的函数应该被调用... - Blue Diamond
@BlueDiamond,如果你在派生类中没有定义虚函数,那么基类的虚函数将被调用,但由于你正在覆盖它,因此这里将调用派生类的虚函数。 - Santosh Sahu
@SantoshSahu 这是否意味着每个派生类都有自己的vptr,还是vptr从基类继承到派生类? - Chandra Shekhar
@ChandraShekhar,是的,每个派生类都有自己的vptr,并指向vtable。它不能被继承,因为在对象初始化期间,vptr指向类vtable的第一个条目。vtable条目包含函数指针,如果派生类中未覆盖相同类别的函数,则vtable可以具有基类函数指针的条目。希望这能澄清你的疑惑。 - Santosh Sahu

3
而bptr->fun()将被解析为bptr->vptr->fun(); 这指向基类函数本身。
错误。Derived实例的vptr(每个实例中的隐藏字段)指向Derived vtable。

1
好的,你明白每个类都有自己的虚函数表(vtable)(它指出每个虚函数应该使用哪个实现),并且每个实例的虚函数指针(vptr)指向正确的虚函数表,匹配对象的实际(动态)类型。还缺少什么? - Kos
也许缺失的链接是Derived的“fun”也是(隐式)虚拟的?这是因为两个“fun”类型签名是相同的。 - Kos
Vtable 只包含虚函数的地址。但是派生函数如何被调用呢?Vtable 不包含非虚函数的地址。 - Blue Diamond
1
非虚函数显然是在编译时选择的。在您提供的代码中,派生类的 fun 也是虚拟的(隐式),因为基类中有一个具有相同签名的 virtual fun。更改名称或添加参数,看看会发生什么。 - Kos
以上代码输出了预期的结果。但是我对其中的机制不是很清楚。派生类中有两个具有相同名称/签名的函数。一个是继承的虚拟fun(),另一个是重写的非虚拟fun()。派生类的V表只包含继承的虚拟fun()的地址,那么另一个函数如何被调用,即非虚拟函数? - Blue Diamond

0

标准并没有规定多态性的实现机制。标准只是说明了它应该如何工作,而不是编译器供应商应该如何实现它。

话虽如此,就Linux下的GCC和Windows下的MSVC而言,你的理解基本上是正确的,我也预计大多数其他编译器会类似。


有道理。Vtable 只包含虚函数的地址。但是派生函数如何被调用呢?Vtable 不包含非虚函数的地址。我想搞清楚这一点... - Blue Diamond

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接