最近在看C++的一些相关的机制,再加上刚看了陈皓大神的早期关于虚函数表的博客,便自己动手通过编程了解了下虚函数表的原理。
前言
c++是通过虚函数来实现多态的的机制。我们可以通过将父类的指针指向子类的实例,如Base b = new Derive()
,如此一来,如果子类Derive
中重载了父类中的一个函数h()
,那么调用b->h()
等同于调用Derive d
, d->h()
。我们这里来剖析下虚函数表的实现机制。
1 虚函数表的内部结构
我们可以通过编程来对虚函数表的内部结构来一窥究竟。如下代码:
#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; }
};
我们构造了一个基类,定义了三个虚函数f()
,g()
,h()
,分别输出对应的描述。我们通过以下代码来得到该基类的虚函数表地址,和虚函数表中各个函数的地址。
int main() {
Base1 b;
cout << "虚函数表地址:" << (long*)(&b)+1 << endl;
cout << "虚函数表-第一个函数地址:" << ((long*)*(long*)(&b)) << endl;
cout << "虚函数表-第二个函数地址:" << ((long*)*(long*)(&b)+1) << endl;
cout << "虚函数表-第三个函数地址:" << ((long*)*(long*)(&b)+2) << endl;
}
我的机器是64位系统,其中函数指针大小为16位,因此使用long*
强转。运行结果如下:
虚函数表地址:0x7fffb023d7a8
虚函数表-第一个函数地址:0x466c88
虚函数表-第二个函数地址:0x466c90
虚函数表-第三个函数地址:0x466c98
为了进一步验证这些函数地址的有效性,使用一个函数指针pFun
来指向这些函数:
int main() {
typedef void(*Fun)(void);
Base1 b;
Fun pFun = NULL;
pFun = (Fun)*((long*)*(long*)(&b));
pFun();
pFun = (Fun)*((long*)*(long*)(&b)+1);
pFun();
pFun = (Fun)*((long*)*(long*)(&b)+2);
pFun();
}
结果如下:
Base::f
Base::g
Base::h
由此我们可知该基类的虚函数表的结构应该是这样:
2 一般继承无函数重写的情况
我们来分析单继承有子类的情况,而且该子类有自己的虚函数,并无对父类的函数进行重写。数据结构如下:
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:
virtual void f1() { cout << "Derive::f1" << endl; }
virtual void g1() { cout << "Derive::g1" << endl; }
virtual void h1() { cout << "Derive::h1" << endl; }
};
通过编程验证可知,其父类的虚函数表没有变化,其子类的虚函数表是这样:
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; }
};
class Derive{
public:
void f() { cout << "Derive::f" << endl; }
virtual void g1() { cout << "Derive::g1" << endl; }
virtual void h1() { cout << "Derive::h1" << endl; }
};
可以看出子类重写了父类的 f 函数,这时候再看子类的虚函数表结构:
可以看出子类的f函数覆盖了父类的f函数的位置,其他位置不变。
不妨编程验证一下:
int main() {
typedef void(*Fun)(void);
Base b;
Derive d;
Fun pFun = NULL;
pFun = (Fun)*(long*)*((long*)(&d));
pFun();
}
结果:
Derive::f
4 多重继承无函数重写的情况
待续,,,