C++虚函数探索笔记(1)——虚函数的简单示例分析
关注问题:
虚函数的作用
虚函数的实现原理
虚函数表在对象布局里的位置
虚函数的类的sizeof
纯虚函数的作用
多级继承时的虚函数表内容
虚函数如何执行父类代码
多继承时的虚函数表定位,以及对象布局
虚析构函数的作用
虚函数在QT的信号与槽中的应用
虚函数与inline修饰符,static修饰符
啰嗦两句
虚函数在C++里的作用是在是非常非常的大,很多讲述C++的文章都会讲到它,要用好C++,就一定要学好虚函数。网络上可以google到很多很多关于它的文章,这一次的学习,我不准备去只是简单的阅读了解那些文章,而是希望通过编写一些测试代码,来对虚函数的一些实现机制,以及C++对象布局做一下探索。
虚函数的简单示例 !
虚函数常常出现在一些抽象接口类定义里,当然,还有一个更常见的“特例”,那就是虚析构函数,后面会提到这个。
下面是一段关于虚函数的简单代码,演示了使用基类接口操作对象时的效果:
//Source filename: Win32Con.cpp
#include <iostream>
using namespace std;
class parent1
{
public:
virtual int fun1()=0;
};
class child1:public parent1
{
public:
virtual int fun1()
{
cout<<"child1::fun1()"<<endl;
return 0;
}
};
class child2:public parent1
{
public:
virtual int fun1()
{
cout<<"child2::fun1()"<<endl;
return 0;
}
};
void test_func1(parent1 *pp)
{
pp->fun1();
}
int main(int argc, char* argv[])
{
child1 co1;
child2 co2;
test_func1(&co1);
test_func1(&co2);
return 0;
}
在上面的代码里,类parent1是一个只具有纯虚函数的接口类,这个类不能被实例化,它唯一的用途就是抽象一些特定的接口函数,当然,在这里这个接口函数就是纯虚函数 parent1::fun1()。
而类child1和child2则是两个从parent1继承的类,我们要使用它定义具体的类实例,所以它实现了由parent1继承得来的fun1接口,并且各自的实现是不同的。
函数 test_func1 的参数是一个parent1类型的指针,它所要完成的功能就是调用这个parent1对象的fun1()函数。
让我们编译运行一下上面的代码,可以看到下面的输出
child1::fun1()
child2::fun1()
很显然,在两次调用test_func1函数的时候,虽然传入的参数都是一个parent1的指针,但是却都分别执行了child1和child2各自的fun1函数!这就是C++里类的多态。然而,这一切是怎么发生的呢?test_func1函数怎么会知道应该调用哪个函数的呢?我不准备像其他人一样画若干图来说明,我准备用具体某个编译器产生的对象布局以及相应的汇编代码来说明这个过程(这个编译器是vs2008里的vc9)。
我们先打开一个VS2008命令提示窗口,改变目录到上面的代码Win32Con.cpp所在目录,输入下面的命令:
cl win32con.cpp /d1reportSingleClassLayoutchild
上面的命令可以编译win32con.cpp源码,同时生成里面类名包含child 的类的对象布局(layout)
注意:d1reportSingleClassLayout和后面的child是相连的!
输入上面的命令后看到的对象布局如下,红色字为我添加的注释
class child1 size(4): 子类child1的对象布局,只包含一个vfptr,大小为4字节
+---
| +--- (base class parent1) 这是被嵌套的父类parent1的对象布局
0 | | {vfptr}
| +---
+---
这是child1的vfptr所指的虚函数表的布局,只包含一个函数的地址,就是child1的fun1函数
child1::$vftable@:
| &child1_meta
| 0
0 | &child1::fun1
child1::fun1 this adjustor: 0
class child2 size(4): 子类child2的对象布局,只包含一个vfptr,大小为4字节
+---
| +--- (base class parent1) 这是被嵌套的父类parent1的对象布局
0 | | {vfptr}
| +---
+---
这是child2的vfptr所指的虚函数表的布局,只包含一个函数的地址,就是child2的fun1函数
child2::$vftable@:
| &child2_meta
| 0
0 | &child2::fun1
child2::fun1 this adjustor: 0
从上面的对象布局可以知道:
每个子对象都有一个隐藏的成员变量vfptr(你当然不能用这个名字访问到它),它的值是指向该子对象的虚函数表,而虚函数表里填写的函数地址是该子对象的fun1函数地址。