虚函数调用机制

时间:2011-09-27 06:33:04

标签: c++ inheritance

class base {
public:
  virtual void fn(){}
};


class der: public base {
public:
  void fn(){}
};

der d;

base *b = &d;
b->fn();

当编译器遇到语句b->fn()时,编译器可以使用以下信息:

  1. b是指向类库的指针
  2. 基类具有虚函数和vptr。
  3. 我的问题是:在运行时,类的vptr如何进入画面?

6 个答案:

答案 0 :(得分:4)

Holy Standard不需要vptr或vptr表。但实际上,这是实现这一目标的唯一方法。

所以这里是psudo代码,用于发生的事情:

  1. a_base_compatible_vtable_ptr = b->__vtable_ptr__
  2. a_func_ptr = a_base_compatible_vtable_ptr[INDEX_FOR_fn]
  3. a_func_ptr( b )
  4. 主要的见解是,对于der类的对象,对象中的vtable指针将指向der类'vtable,它与兼容 base class'vtable,但包含指向der类'函数实现的指针。

    因此,调用函数的der实现。

    实际上,通过将this指针传递到专用处理器寄存器而不是机器堆栈中,通常在第(3)点传递的this指针参数是特殊优化的。

    有关更深入的讨论,请参阅有关C ++内存模型的文献,例如: Stanly Lippman’s book Inside the C++ Object Model

    干杯&第h。,

答案 1 :(得分:3)

在对此进行推理时,它有助于我保持类的内存布局的清晰图像,特别是der对象包含 a {{1与任何其他base对象具有完全相同的内存布局的子对象。

特别是你的base对象布局只包含一个指向vtable的指针(没有字段),而base base子对象也将包含该指针,只有存储在指针中的值不同,它将引用der vtable的der版本(为了使它更有趣,请考虑basebase包含成员):

der

如果查看这两个图,您可以识别// Base object // base vtable (ignoring type info) +-------------+ +-----------+ | base::vptr |------> | &base::fn | +-------------+ +-----------+ | base fields | +-------------+ // Derived object // der vtable +-------------+ +-----------+ | base::vptr |------> | &der::fn | +-------------+ +-----------+ | base fields | +-------------+ <----- [ base subobject ends here ] | der fields | +-------------+ 对象中的base子对象,当您执行der时,您正在做的是获取指向{{}的指针1}}子对象 base *bp = &d;内。在这种情况下,base子对象的内存位置与der子对象的内存位置完全相同,但不一定如此。重要的是指针将引用base子对象,并且指向的内存具有base的内存布局,但区别在于存储在对象中的指针将引用vtable的base版本。

当编译器看到代码base时,它会认为它是der对象,它知道 vptr bp->fn()中的位置对象,它也知道base是vtable中的第一个条目,因此它只需要为base生成代码。如果fn引用bp->vptr[ 0 ]()对象,则bp将引用base vtable ,而bp->vptr将引用base }。如果另一方面指针指向bp->vptr[0]对象,则base::fn将引用der vtable,bp->vptr将引用der。< / p>

请注意,在编译时,两种情况的生成代码完全相同:bp->vptr[0],并根据存储在der::fn(子)对象中的数据将其分派到不同的函数,特别是存储在bp->vptr[0]()中的值,它在构造中得到更新。

明确关注base子对象必须存在且兼容vptr对象的事实,您可以考虑更复杂的场景,作为多重继承:

base

这是一个更有趣的案例,其中有另一个基础,此时调用base创建指向struct data { int x; }; class other : public data, public base { int y; public: virtual void fn() {} }; +-------------+ | data::x | +-------------+ <----- [ base subobject starts here ] | base::vptr | +-------------+ | base fields | +-------------+ <----- [ base subobject ends here ] | other::y | +-------------+ int main() { other o; base *bp = o; } 子对象的指针,并且可以验证它指向与{不同的位置{1}}对象(尝试打印出base * bp = o;base的值)。从调用站点来看,这并不重要,因为o具有静态类型&o,并且编译器总是可以取消引用该指针以找到bp,使用它来定位bp在vtable中,最后调用base*

在这个例子中还有一点神奇之处,因为base::vptrfn子对象在调用实际函数other::fn之前没有对齐,{{1} }指针必须调整。编译器通过不在other vtable 中存储指向base的指针来解析,而是指向虚拟thunk 的指针(小块代码)它修复了other::fn的值并将调用转发给this

答案 2 :(得分:1)

在典型的实现中,每个对象只有一个vptr。如果对象的类型为der,则指针将指向der vtable,而如果类型为base,则指向base vtable。该指针在构造时设置。类似的东西:

class base {
public:
  base() {
    vptr = &vtable_base;
  }
  virtual void fn(){}
protected:
   vtable* vptr;
};


class der: public base {
public:
  der() {
    vptr = &vtable_der;
  }
  void fn(){}
};

b->fn()的调用类似于:

vtable* vptr = b->vptr;
void (*fn_ptr)() = vtpr[fn_index];
fn_ptr(b);

答案 3 :(得分:1)

我找到了Here..

以上的最佳答案

答案 4 :(得分:0)

Vptr是对象的属性,而不是指针的属性。因此,bbase)的静态类型无关紧要。重要的是它的动态类型(der)。 b指向的对象的vptr指向der的虚方法表(vtbl)。

当您致电b->fn()时,会查询der的虚拟方法表,以确定要调用的方法。

答案 5 :(得分:0)

vptr不是“类”,而是“实例化对象”。 构造d时,首先分配它的空间(并包含vptr)。

然后构造base(并且使vptr指向基本vtable)然后在der周围构建base并更新vptr以指向{{1} vtable。

derbase vtable都有der个函数的条目,并且由于vptr引用了一个,当你调用b-&gt; fn()时,事实上发生了对fn()的调用。

但是由于vptr(p)== vptr(&amp; d),在这种情况下,会调用vptr(p)[fn_index]()