虚函数与多态
本文目录 5 个章节
虚函数与多态
# 虚函数与多态
创建时间:2020/3/20 0:49
多态
多态 是指在不同的条件下表现出不同的状态。 编译期间多态:重载函数,根据参数列表的不同寻找合适的函数 运行时多态:虚函数
虚函数
- 纯虚函数:父类没有实现版本,完全交给子类,且必须实现。是虚函数再加上 = 0;
- 半虚函数:父类可以实现,子类需要重写
- 抽象类是指包括至少一个纯虚函数的类。
以下函数不能作为虚函数
- 友元函数,它不是类的成员函数
- 全局函数
- 静态成员函数,它没有this指针
- 构造函数,拷贝构造函数,以及赋值运算符重载
实现原理
为每个类对象添加一个隐藏成员,保存了一个指向 函数地址数组 的 指针 ,称为 虚表指针 (vptr),这种数组成为 虚函数表 (virtual function table, vtbl),即,每个类使用一个虚函数表,每个类对象用一个虚表指针。
如果类中有N个虚函数,那么其虚函数表将有(N * 4)字节的大小。
基类对象包含一个指针,该指针指向基类所有虚函数的地址表,派生类对象将包含一个指向独立地址表的指针,如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址,如果派生类没有重新定义虚函数,该虚函数表将保存函数原始版本的地址。如果派生类定义了新的虚函数,则该函数的地址将被添加到虚函数表中,注意虚函数无论多少个都只需要在对象中添加一个虚函数表的地址。 调用虚函数时,程序将查看存储在对象中的虚函数表地址,转向相应的虚函数表,使用类声明中定义的第几个虚函数,程序就使用数组的第几个函数地址,并执行该函数。

关于对象大小问题 有虚函数或虚继承的类实例化后的 对象大小 至少为4字节(确切的说是一个指针的字节数;说至少是因为还要加上其他非静态数据成员,还要考虑对齐问题);没有虚函数和虚继承的类实例化后的对象大小至少为1字节(没有非静态数据成员的情况下也要有1个字节来记录它的地址)。
其他问题
(1)当存在类继承并且析构函数中有必须要进行的操作时(如需要释放某些资源,或执行特定的函数)析构函数需要是虚函数,否则若使用父类指针指向子类对象,在delete时只会调用父类的析构函数,而不能调用子类的析构函数,从而造成内存泄露或达不到预期结果; (2)内联函数不能为虚函数:内联函数需要在编译阶段展开,而虚函数是运行时动态绑定的,编译时无法展开; (3)构造函数不能为虚函数:构造函数在进行调用时还不存在父类和子类的概念,父类只会调用父类的构造函数,子类调用子类的,因此不存在动态绑定的概念;但是构造函数中可以调用虚函数,不过并没有动态效果,只会调用本类中的对应函数; (4)静态成员函数不能为虚函数:静态成员函数是以类为单位的函数,与具体对象无关,虚函数是与对象动态绑定的。
为何构造函数不能是虚函数
- 从存储空间角度: 虚函数对应一个vtable,要占存储空间。如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,无法找到vtable,所以构造函数不能是虚函数。
- vbtl在构造函数调用后才建立,因而构造函数不可能成为虚函数