1
#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 语言中都很难理解。

4

2 回答 2

5

随着对象构造的发生,它分步进行。第一步是初始化基类,在这个过程中,对象就是基对象——vtable 将反映基类的方法。当构造进展到派生类时,vtable 被更新为派生类的 vtable。

这都是由 C++ 标准规定的,当然,除了该标准不强制实现 vtables。

于 2012-06-05T04:12:23.070 回答
3

您刚刚见证了 C++ 编译器如何实现 C++ 标准规定的规则。在Base构造函数运行时,对虚拟方法的任何调用都需要分派给Base实现,即使Drive重写它们也是如此。为了实现这一点,您的 C++ 编译器显然使对象的 vtable 指针指向Basevtable。当Base构造函数完成并在Drive构造函数中继续执行时,vtable 指针被更新为指向Drivevtable。

这最终成为实现它的便捷方式。编译器生成调用虚方法的指令的其余代码不需要更改以检测是否需要特殊的构造函数行为。它可以像往常一样在 vtable 中查看。更改 vtable 指针是在构造函数运行时更改对象的有效运行时类型的快速方法。

您可能会发现析构函数以类似的方式工作,但相反。如果您构造一个Base对象而不是一个Drive对象,您可能会看到与您在对象的Base构造函数中所做的相似的地址Drive

于 2012-06-05T04:17:07.670 回答