C++ 虚函数表及多态内部原理详解(一)
C++中 的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术 可以让父类的指针有“多种形态”,这是一种泛型技术。
虚函数表
每个含有虚函数的类都有一个虚函数表(Virtual Table)来实现的。简称为V-Table。 C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。
1、 每一个类都有虚函数列表。
2、 虚表可以继承,如果子类没有重写虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的虚函数实现。如果基类3个虚函数,那么基类的虚表中就有三项(虚函数地址),派生类也会有虚表,至少有三项,如果重写了相应的虚函数,那么虚表中的地址就会改变,指向自身的虚函数实现。如果派生类有自己的虚函数,那么虚表中就会添加该项。
3、 派生类的虚表中虚函数地址的排列顺序和基类的虚表中虚函数地址排列顺序相同,子类独有的虚函数放在后面。
当定义一个有虚函数类的对象时,对象的第一块的内存空间就是一个指向虚函数列表的指针。
在这举个例子
假设我们有这样的一个类:
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
按照上面的说法,我们可以通过Base的实例来得到虚函数表。代码如下:
typedef void(*Fun)(void);
Base b;
Fun pFun = NULL;
cout << "虚函数表地址:" << (int*)(&b) << endl;
cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl;
// Invoke the first virtual function
pFun = (Fun)*((int*)*(int*)(&b));
pFun();
pFun=(Fun)*((int*)*(int*)(&b)+1);
pFun();
pFun=(Fun)*((int*)*(int*)(&b)+2);
pFun();
pFun=(Fun)*((int*)*(int*)(&b)+3);
cout<<pFun<<endl;
return 0;
运行结果如下:
在程序中取出对象b的地址,根据对象的布局可以得出就是虚表的地址,根据这个地址可以把虚表的第一个内存单元的内容取出,然后强制转换成一个函数指针,利用这个函数指针来访问虚函数。又因为虚表是连续的,利用+1可以来访问下一个内存单元。最后一个输出0表明后面没有虚表了。
如果对代码不理解的话,可以看这幅图就会懂了
注意:虚函数表在最后会有一个结束标志,为1说明还有虚表,为0表示没有虚表了 。(编译器不同,结束标志可能存在差异)
下面,将分别具体说明“无虚函数覆盖”和“有虚函数覆盖”时的虚函数表的情况。
(一)无虚函数覆盖
没有任何的继承,虚函数表如下图
代码如下:
#include<iostream>
using namespace std;
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
class Derive : public Base
{
public:
virtual void f1() { cout << "Derive::f1" << endl; }
virtual void g1() { cout << "Derive::g1" << endl; }
virtual void h1() { cout << "Derive::h1" << endl; }
};
typedef void(*Fun)(void);
int main(){
Derive d;
Fun pFun = NULL;
cout << "虚函数表地址:" << (int*)(&d) << endl;
cout << "虚函数表 - 第一个函数地址:" << (int*)*(int*)(&d) << endl;
// Invoke the first virtual function
pFun = (Fun)*((int*)*(int*)(&d));
pFun();
pFun=(Fun)*((int*)*(int*)(&d)+1);
pFun();
pFun=(Fun)*((int*)*(int*)(&d)+2);
pFun();
pFun=(Fun)*((int*)*(int*)(&d)+3);
pFun();
pFun=(Fun)*((int*)*(int*)(&d)+4);
pFun();
pFun=(Fun)*((int*)*(int*)(&d)+5);
pFun();
return 0;
}
运行结果如下图所示
和上一个程序一样,根据取出虚表里面的地址强制转换成函数指针,调用对应的虚函数。可以得出虚函数按照其声明顺序存放于虚函数表中的,子类自己的虚函数是排在父类虚函数之后的。
先讲到这,明天继续!有兴趣的请加我微信交流