虚函数调用机制
Virtual function calling mechanism
class base {
public:
virtual void fn(){}
};
class der: public base {
public:
void fn(){}
};
der d;
base *b = &d;
b->fn();
当编译器遇到语句b->fn(
)时,编译器可以获得以下信息:
- b是指向类基的指针,
- 基类具有虚函数和vptr。
我的问题是:class der的vptr是如何在运行时出现的?
神圣标准不需要vptr或vptr表。然而,在实践中,这是唯一的实现方式。
所以这里’s伪代码发生了什么:
-
a_base_compatible_vtable_ptr = b->__vtable_ptr__
-
a_func_ptr = a_base_compatible_vtable_ptr[INDEX_FOR_fn]
-
a_func_ptr( b )
一个主要的见解是,对于der
类的对象,对象中的虚函数表指针将指向der
类’与base
类兼容;vtable,但包含指向der
类的指针。函数实现。
因此,调用函数的der
实现。
实际上,在(3)点传递的this
指针参数通常是经过优化的,特殊的,通过将this
指针传递到专用处理器寄存器中,而不是在机器堆栈中。
有关更深入的讨论,请参阅有关c++内存模型的文献,例如stanley lippman的著作Inside the c++ Object model 。
干杯,hth。,
在推理这一点时,它有助于我保持类的内存布局的清晰图像,特别是der
对象包含 base
子对象,该子对象具有与任何其他base
对象完全相同的内存布局。
特别是你的base
对象布局将简单地包含一个指向vtable的指针(没有字段),der
的base
子对象也将包含该指针,只有存储在指针中的值不同,它将引用base
vtable的der
版本(为了使其更有趣,考虑base
和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 |
+-------------+
如果你看这两张图,你可以在der
对象中识别base
子对象,当你做base *bp = &d;
时,你正在做的是在 der
中获得一个指向base
子对象的指针。在这种情况下,base
子对象的内存位置与base
子对象的内存位置完全相同,但不必如此。重要的是,指针将指向base
子对象,并且指向的内存具有base
的内存布局,但不同的是,存储在对象中的指针将指向der
版本的vtable。
当编译器看到代码bp->fn()
时,会认为它是一个base
对象,并且知道vptr在base
对象中的位置,也知道fn
是虚表中的第一项,所以只需要生成bp->vptr[ 0 ]()
的代码。如果bp
指向base
对象,那么bp->vptr
指向base
虚表, bp->vptr[0]
指向base::fn
。另一方面,如果指针指向der
对象,则bp->vptr
指向der
虚表,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;
}
这是一个更有趣的情况,这里有另一个基数,此时调用base * bp = o;
创建了一个指向base
子对象的指针,并且可以验证指向与o
对象不同的位置(尝试打印出&o
和bp
的值)。从调用站点来看,这并不重要,因为bp
具有静态类型base*
,编译器总是可以解引用该指针来定位base::vptr
,使用该指针在虚函数表中定位fn
,并最终调用other::fn
。
在这个例子中有更多的魔力,因为other
和base
子对象没有对齐,在调用实际的函数other::fn
之前,必须调整this
指针。编译器通过不在other
虚表中存储指向other::fn
的指针来解决这个问题,而是在虚表中存储指向的指针(一小段代码固定了this
的值并将调用转发给other::fn
)
在典型的实现中,每个对象只有一个vptr
。如果对象的类型是der
,指针将指向der
虚表,而如果对象的类型是base
,指针则指向base
虚表。该指针在构造时设置。类似这样:
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);
我在这里找到了最好的答案。
Vptr是对象的属性,而不是指针的属性。因此,b
(base
)的静态类型无关紧要。重要的是它的动态类型(der
)。b
所指向的对象的vptr指向der
的虚方法表(vtbl)。
当你调用b->fn()
时,它是der
的虚拟方法表,它被咨询以确定要调用哪个方法。
vptr不是"类的",而是"实例化对象"的。当构造d
时,首先为它分配空间(也包含vptr)。
然后构造base
(vptr指向基虚表),然后围绕base
构造der
,并更新vptr指向der
虚表。
base
和der
虚表都有fn()
函数的表项,由于vptr指向前者,当调用b->fn()时,实际上会调用vptr(p)[fn_index]()
。
但是由于vptr(p) == vptr(&d),在这种情况下,对der::fn
的调用将返回
- 函数调用中参数的顺序重要吗
- 基于另一个成员参数将函数调用从类传递给它的一个成员
- 变量没有改变?通过向量的函数调用
- 在两个类中共享相同的函数调用,并在不需要时避免空实例化
- 是否有C++编译器选项允许激进地删除所有函数调用,并将参数传递给具有空体的函数
- 我知道函数调用中存在歧义.有没有办法调用foo()函数
- 模板函数调用
- 获取从C++中同一类中的构造函数调用的方法返回的值
- 析构函数调用
- 成员函数调用和C++对象模型
- 使用共享指针的函数调用,其对象应为 const
- C++:编译时检查匹配的函数调用对?
- 函数调用C++中的参数太少
- 来自 DLL 的函数调用 [表观调用的括号前面的表达式必须具有(指向-)函数类型]
- 返回指向对象的指针的函数调用是否为 prvalue?
- C++ 如何重载 [] 运算符并进行函数调用
- 代码的效率. 转到和函数调用
- 是同一作用域的函数部分中的函数调用
- 如何封装一个函数,以便它只能由同一类中的一个其他函数调用?
- 虚函数调用机制