虚函数的主要作用是实现了多态的机制。关于多态,简而言之就是用父类型的指针指向其子类的示例,然后通过父类的指针调用实际子类的成员函数。
这种技术可以让父类的指针有多种形态,这是一种泛型技术。
泛型技术其实就是试图在使用不变的代码来实现可变的算法。比如:模板技术、虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。
·
虚函数表
C++为了实现虚函数使用了一种动态绑定的技术。就是通过一张虚函数表(Virtual Table)来实现,简称为V-Table。在这个表中,主要是一个类的虚函数的地址表。每个包含了虚函数的类中,都包含了一个虚表。父类里边包含了虚函数,那么当子类继承父类的时候,也会拥有自己的虚表。
虚表是一个指针数组,其元素是虚函数的指针,每个元素对应一个虚函数的函数指针。因为普通函数的调用其实是不需要经过虚表的,所以虚表的元素并不包括普通函数的函数指针。
虚函数指针的赋值发生在编译器的编译阶段,在这个时候虚表就已经构造出来了。
虚表指针
虚表是属于类的,而不是属于某个具体的对象,一个类只需要一个虚表即可,同一个类的所有对象都使用同一个虚表。
为了指定对象的虚表,对象的内部一般都包含了一个虚表的指针,来指向自己所使用的虚表。
为了让每个包含虚表的类的对象都拥有一个虚表指针,编译器在类中添加了一个指针,*_vptr用来指向虚表。当类的对象在创建时就拥有了这个指针。且这个指针的值会自动被设置为指向类的虚表
动态绑定
class A {public:virtual void vfunc1();virtual void vfunc2();void func1();void func2();private:int m_data1, m_data2;};class B : public A {public:virtual void vfunc1();void func1();private:int m_data3;};class C: public B {public:virtual void vfunc2();void func2();private:int m_data1, m_data4;};
A是基类,B是A的子类,C继承与B,其对象模型如下:
那么动态绑定是怎么实现的呢?
假设我们声明一个类A的指针p来指向对象B:
ClassB b;
ClassA *a = &b;
a->func();
程序在执行a->func()的时候,会发现a是一个指针,且调用的函数是虚函数,接下来便会进行以下的步骤:
- 根据虚表指针
a->__vptr来访问ClassB对应的虚表。虽然指针只能指向基类的部分,但是虚表指针就是属于基类部分,所以指针可以访问到ClassB的虚表指针。 - 在虚表中查找所调用的函数对应的条目。由于虚表在编译节点就构造出来了。所以可以根据所调用的函数定位到虚表中对应的条目。
所以,通过使用虚函数表,即使是基类的指针来调用函数,也是可以正确调用运行中实际对象的函数。
我们把经过虚表调用虚函数的过程称为动态绑定,其表现出来的线程成为动态多态。
总结
- 为了实现多态,C++使用了动态绑定技术。这个技术的核心是虚函数
- 带虚函数的类都拥有一张虚表,虚表属于类不属于对象
- 带虚函数的对象在创建的时候会自自动创建并赋值一个虚表指针
- 子类继承出来的虚表指针*_vptr也是基类的一部分
封装、继承、多态是面向对象设计的三个特征,而多台可以说是面向对象设计的关键。C++通过虚函数表,实现了虚函数与对象的动态绑定,从而构建了C++面向对象程序设计的技术
