class base {
public:
virtual void fn(){}
};
class der: public base {
public:
void fn(){}
};
der d;
base *b = &d;
b->fn();
当编译器遇到语句b->fn(
)时,编译器可以使用以下信息:
我的问题是:在运行时,类的vptr如何进入画面?
答案 0 :(得分:4)
Holy Standard不需要vptr或vptr表。但实际上,这是实现这一目标的唯一方法。
所以这里是psudo代码,用于发生的事情:
a_base_compatible_vtable_ptr = b->__vtable_ptr__
a_func_ptr = a_base_compatible_vtable_ptr[INDEX_FOR_fn]
a_func_ptr( b )
主要的见解是,对于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
版本(为了使它更有趣,请考虑base
和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 |
+-------------+
对象中的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::vptr
和fn
子对象在调用实际函数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是对象的属性,而不是指针的属性。因此,b
(base
)的静态类型无关紧要。重要的是它的动态类型(der
)。 b
指向的对象的vptr指向der
的虚方法表(vtbl)。
当您致电b->fn()
时,会查询der
的虚拟方法表,以确定要调用的方法。
答案 5 :(得分:0)
vptr不是“类”,而是“实例化对象”。
构造d
时,首先分配它的空间(并包含vptr)。
然后构造base
(并且使vptr指向基本vtable)然后在der
周围构建base
并更新vptr以指向{{1} vtable。
der
和base
vtable都有der
个函数的条目,并且由于vptr引用了一个,当你调用b-&gt; fn()时,事实上发生了对fn()
的调用。
但是由于vptr(p)== vptr(&amp; d),在这种情况下,会调用vptr(p)[fn_index]()
。