多态
我们知道继承是代码复用的一种体现,而多态是接口复用的一种体现,即:同一接口不同形态。
- 静多态(函数重载/模板)
编译阶段确定函数的调用 - 动多态(虚函数)
运行阶段确定函数的调用 - 宏多态(不常用)
预编译阶段确定函数的调用
动多态:由虚函数机制提供支持
- 虚函数处理机制:
①编译阶段:将函数入口地址写入在只读数据段(.rodata段),具体存放在虚函数表(vftale)中。
②链接完成后:函数入口地址存放在只读数据段
③运行阶段:将指令和数据(虚函数入口地址)加载到内存中
虚函数表(vftable):
当类中存在虚函数,系统为实现对象和虚函数表的共享关系,会在对象的内存中设计一个虚函数指针指向虚表。
派生类会继承基类的虚函数,在虚函数表中覆盖派生类和基类的同名同参的虚函数。
函数成为虚函数的条件:
- 能取地址
- 依赖对象调用
多态处理流程:
基类指针指向派生类对象调用接口时,会去找到虚函数指针然后解引用,去虚表中找到需要调动的函数
虚析构
当基类指针指向派生类对象,应该将基类的析构设置为虚函数,此时派生类的析构函数也会变成虚函数。
多态的发生时机:
- 指针调用虚函数
- 对象完整(构造完成,未析构)
虚表的写入时机:
构造之前
纯虚函数:
①保留接口
②不能实现
多重继承(菱形继承):
class A
{
public:
A(int a) :ma(a){}
public:
int ma;
};
class B :public A
{
public:
B(int b) :mb(b), A(b){}
public:
int mb;
};
class C :public A
{
public:
C(int c) :mc(c), A(c){}
public:
int mc;
};
class D :public B, public C
{
D(int d) :md(d), B(d), C(d){}
public:
int md;
};
此时D类的内存布局如下图所示:
当D类对象调用ma时,不知道是调用从B继承的还是调用从C继承的,程序出错。此时引入虚继承。
虚继承:
处理方式:
- 将虚基类的数据放在当前作用域的最下面
- 在原位置补一个虚基类指针(vbptr)
- 虚基类指针指向一个虚基类表(vbtable)
虚基类表(vbtable):
内存布局:
- 非虚基类的布局优先于虚基类
- 虚基类的处理和继承顺序有关
- 虚基类指针也要合并(向内侧合并)