#include <iostream>
using namespace std;
class Base {
public:
Base() {
cout << "In Base" << endl;
cout << "Virtual Pointer = " << (int*)this << endl;
cout << "Address of Vtable = "
<< (int*)*(int*)this << endl;
cout << "Value at Vtable = "
<< (int*)*(int*)*(int*)this << endl;
cout << endl;
}
virtual void f1() { cout << "Base::f1" << endl; }
};
class Drive : public Base {
public:
Drive() {
cout << "In Drive" << endl;
cout << "Virtual Pointer = "
<< (int*)this << endl;
cout << "Address of Vtable = "
<< (int*)*(int*)this << endl;
cout << "Value at Vtable = "
<< (int*)*(int*)*(int*)this << endl;
cout << endl;
}
virtual void f1() { cout << "Drive::f2" << endl; }
};
int main() {
Drive d;
return 0;
}
该程序的输出是
In Base
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C08C
Value at Vtable = 004010F0
In Drive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C07C
Value at Vtable = 00401217
按照代码,我可以看到,当我创建一个Drive对象时,Base构造函数也会运行并显示与Drive的虚拟指针相同的虚拟指针的地址:0012FF7C。奇怪的是,当我在Base和Drive类的构造函数中取消引用该地址时,它指向不同的值意味着有两个vtable,一个在0046C08C,另一个在0046C07C。当1指针指向2地址时,它很难理解Drive对象的结构以及c langue。
答案 0 :(得分:5)
当对象构造发生时,它会逐步发生。第一步是初始化基类,在该过程中对象是基础对象 - vtable将反映基类的方法。当构造进行到派生类时,vtable将更新为派生类的vtable。
这完全由C ++标准规定,当然标准不强制要求实现vtable。
答案 1 :(得分:3)
您刚刚目睹了C ++编译器如何实现C ++标准规定的规则。当Base
构造函数正在运行时,需要将对虚拟方法的任何调用分派到Base
实现,即使Drive
覆盖它们也是如此。为了实现这一点,您的C ++编译器显然使对象的vtable指针指向Base
vtable。当Base
构造函数完成并在Drive
构造函数中继续执行时,vtable指针会更新为指向Drive
vtable。
这最终成为实现它的便捷方式。编译器生成调用虚拟方法的指令的其余代码不需要更改以检测是否需要特殊的构造函数行为。它可以像往常一样看待vtable。更改vtable指针是在构造函数运行时更改对象的有效运行时类型的快速方法。
您可能会发现析构函数以类似的方式工作,但相反。如果构造Base
对象而不是Drive
对象,则可能会看到与Base
对象的Drive
构造函数中的地址类似的地址。