虚函数表与动态绑定
一.单继承虚表模型
•包含虚函数的类
–class B
{
virtual int f1 (void);
virtual void f2 (int);
virtual int f3 (int);
};
•编译器会为每个包含虚函数的类生成一张虚函数表,即存放每个虚函数地址的函数指针数组,简称虚表(vtbl),每个虚函数对应一个虚函数表中的索引号
–0 -> B::f1
1 -> B::f2
2 -> B::f3
除了为包含虚函数的类生成虚函数表以外,编译器还会为该类增加一个隐式的成员变量,
通常在该类实例化对象的起始位置,用于存放虚函数表的首地址,该变量被称为虚函数表指针,简称虚指针(vptr)
•代码:B* pb = new B;
pb->f3 (12);
将被编译为:pb->vptr[2] (pb, 12); // B::f3
调用对象的地址被做为this指针,传递给成员函数的第一个(看不见的)形参
•虚表是一个类一张,而不是一个对象一张,同一个类的多个对象,通过各自的虚指针,共享同一张虚表
•继承自B的子类
–class D : public B
{
int f1 (void);
int f3 (int);
virtual void f4 (void);
};
•子类覆盖了基类的f1和f3,继承了基类的f2,增加了自己的f4,编译器同样会为子类生成一张专属于它的虚表
–0 -> D::f1
1 -> B::f2
2 -> D::f3
3 -> D::f4
•指向子类虚表的虚指针就存放在子类对象的基类子对象中,通常在起始位置
•代码: B* pb = new D; pb->f3 (12);
被编译为: pb->vptr[2] (pb, 12); // D::f3
而这就是所谓的多态
二.何为动态绑定
•当编译器看到通过指针或引用调用虚函数的语句时,并不急于生成有关函数调用的指令,相反它会用一段代码替代该语句,这段代码在运行时被执行,完成如下操作
1.确定调用指针或引用的目标对象的真实类型
2.从调用指针或引用的目标对象中找到虚函数表,并从虚函数表中获取所调用虚函数的入口地址
3.根据入口地址,调用该函数
三.动态绑定对性能的影响
•虚函数表本身会增加内存空间的开销
•与普通函数调用相比,虚函数调用要多出几个步骤,增加运行时间的开销
•动态绑定会妨碍编译器通过内联来优化代码
•只有在确实需要多态特性的场合才使用虚函数,否则尽量使用普通函数