C++中类的成员函数默认情况下是non-virtual,即被调用时为静态绑定。
至少 包含一个virtual成员函数的类,都有一个VTABLE——虚函数映射表,表中的每项对应类中一个virtual成员函数的函数体地址。相应的该类的每个对象在为其分配存储空间时,编译器会额外的为每个对象附加一个指针VPTR,该指针指向该对象所属类的VTABLE。
一定要明确概念,VTABLE是在类这个层次上的概念,而VPTR则是在对象这个层次上的概念。
将VPTR正确设置、指向合适的VTABLE,这是由谁负责完成的?类的构造函数。编译器会自动的在构造函数中插入设置VPTR的代码。
常见的实现中,编译器会将VPTR放在对象所占空间的头部。
对于单重继承关系中的子类对象,其VPTR的设置则经历如下两个阶段
A).首先,在基类构造函数中,VPTR被设置为指向基类的VTABLE
B).之后,在子类构造函数中,VPTR被设置为指向子类的VTABLE
注意,这里只存在一个VPTR,在子对象的构建过程中被重写。
而对于多重继承,有类似的过程,不过子类对象中存在不止一个VPTR。
运行结果
至少 包含一个virtual成员函数的类,都有一个VTABLE——虚函数映射表,表中的每项对应类中一个virtual成员函数的函数体地址。相应的该类的每个对象在为其分配存储空间时,编译器会额外的为每个对象附加一个指针VPTR,该指针指向该对象所属类的VTABLE。
一定要明确概念,VTABLE是在类这个层次上的概念,而VPTR则是在对象这个层次上的概念。
将VPTR正确设置、指向合适的VTABLE,这是由谁负责完成的?类的构造函数。编译器会自动的在构造函数中插入设置VPTR的代码。
常见的实现中,编译器会将VPTR放在对象所占空间的头部。
对于单重继承关系中的子类对象,其VPTR的设置则经历如下两个阶段
A).首先,在基类构造函数中,VPTR被设置为指向基类的VTABLE
B).之后,在子类构造函数中,VPTR被设置为指向子类的VTABLE
注意,这里只存在一个VPTR,在子对象的构建过程中被重写。
而对于多重继承,有类似的过程,不过子类对象中存在不止一个VPTR。
下面是一个简单的验证代码,可以观察VPTR是如何被重写,以及指向何处
#include <iostream>
using namespace std;
class Base
{
public:
int x;
Base():x(0)
{
void * pv=this;
int * pi=static_cast<int *>(pv);
printf("vptr in base ctor point to : %x ",*pi);
}
virtual ~Base(){}
};
class Derived:public Base
{
public:
Derived()
{
void * pv=this;
int * pi=static_cast<int *>(pv);
printf("vptr in derived point to : %x ",*pi);
}
};
int main(int argc, char* argv[])
{
Base Ba;
Derived Da;
void * pv=&Ba;
int * pi=static_cast<int *>(pv);
printf("address of Base's vTable : %x ",(*pi));
pv=&Da;
pi=static_cast<int *>(pv);
printf("address of Derived's vTable : %x ",(*pi));
return 1;
}
运行结果
vptr
in
base
ctor point to : 8048af0
vptr in base ctor point to : 8048af0 // initialized by base ctor
vptr in derived point to : 8048b10 // overwritten by derived ctor
address of Base ' s vTable : 8048af0
address of Derived ' s vTable : 8048b10
vptr in base ctor point to : 8048af0 // initialized by base ctor
vptr in derived point to : 8048b10 // overwritten by derived ctor
address of Base ' s vTable : 8048af0
address of Derived ' s vTable : 8048b10