多态又分为静态多态和动态多态。
静态多态其实就是函数重载,动态多态就是一般我们所说的多态。
多态作为面向对象的三大特征之一,需要另外两大特征:封装、继承的支持。
本文主要讲讲,我看了一点《深度探索C++模型》总结多态是怎样实现的。
1.C++内存布局
C++对象主要可以有如下几种成员:
(1)数据成员
①static数据成员
②nonstatic数据成员
(2)函数成员
①static函数成员
②nonstatic函数成员
③virtual函数成员
其中,nonstatic数据成员和virtual函数成员是要占用,变量的内存空间的。
其他成员都是使用一个公共的静态区域。
具体地讲,每一个类对象会为其每一个nonstatic数据成员持有一个指针,而对于其所有的虚函数,会持有1个指针vptr,这个指针指向一个表vtbl,这个表里每一个位置也是一个指针,指向该类的各个虚函数。
2.多态的实现
在定义父类和子类的时候,父类对象的与子类对象都拥有vptr这个成员,但是父类的vptr指向的vtbl里面的指针指向的虚函数是父类的版本。
子类的vptr指向的btbl里面的指针指向的虚函数是子类的版本。
这个vptr指向的btbl里面放的虚函数是哪一个版本是由构造器、拷贝复制运算符决定的。
如果是构造一个父类对象,就是指向父类的虚函数,如果构造一个子类对象就是指向一个子类的虚函数。
如果把一个子类对象,强制转换为一个父类对象,其vptr也会变为指向父类虚函数的。
但是当我们把一个父类的指针赋值一个指向了子类的指针,这个时候首先会发生切割,将子类多出来的成员给切割掉。
因为不同类型的指针,会限定从该地址开始的后面的范围的内存布局。
但是由于指针赋值,并没有调用子类或者父类的构造器,并且vptr是子类对象与父类对象所共有的,所以并没有被切割。
所以这个时候如果一个指针开始指向了一个子类对象,现在通过这个指针调用的虚函数版本就是子类的,虽然这个指针现在是一个父类指针。
注意:
2016/06/01更新
我用G++ 4.9.2编译器验证出来的一些信息
1.vtbl源自父类的会不会也用来填装子类新加入的虚函数
会。
如果父类没有虚函数,子类有就会在子类额外区间多一个vptr
如果父类有虚函数,父类区间的vptr指向的vtbl也会存放子类新建的虚函数。
2.vtbl会不会同时用来记录虚基类信息
不会。有一个vptr,有一个vbtr
但是如果虚基类里面没有数据成员的话,是不会产生一个vbtr用于访问数据成员的。
3.如果是多重继承的虚拟基类是采用多少个vbtr
多重继承了多少个虚拟基类就会有多少个vbtr
4.如果是多阶继承
对于一阶,如果有单独定义的变量,那么每一阶就会产生一个vbtr
总结:
GCC的这些貌似少了很多优化,但是它好像是讲vptr和vbtr都用一个指针来实现的,然后进行了相应地处理。