C++复习 虚函数

虚析构函数

  • 虚析构函数是为了解决基类的指针指向派生类对象,并用基类的指针删除派生类对象。

虚析构函数使用

class Shape
{
public:
    Shape();                    // 构造函数不能是虚函数
    virtual double calcArea();
    virtual ~Shape();           // 虚析构函数
};
class Circle : public Shape     // 圆形类
{
public:
    virtual double calcArea();
    ...
};
int main()
{
    Shape * shape1 = new Circle(4.0);
    shape1->calcArea();    
    delete shape1;  // 因为Shape有虚析构函数,所以delete释放内存时,先调用子类析构函数,再调用基类析构函数,防止内存泄漏。
    shape1 = NULL;
    return 0;
}

请问虚析构函数是如何执行的?
为什么有了虚析构函数,就能先调用子类的析构函数?

子类和父类执行构造函数和析构函数的顺序

  • 构造函数: 父类的先执行,子类的后执行
  • 析构函数: 父类的后执行,子类的先执行

构造函数不能定义为虚函数,析构函数一般需要定义为虚函数

纯虚函数

纯虚函数是一种特殊的虚函数,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。

virtual int A() = 0;

虚函数与纯虚函数

  • 类里如果声明了虚函数,这个函数是实现的,哪怕是空实现,它的作用就是为了能让这个函数在它的子类里面可以被覆盖(override),这样的话,编译器就可以使用后期绑定来达到多态了。纯虚函数只是一个接口,是个函数的声明而已,它要留到子类里去实现。
  • 虚函数在子类里面可以不重写;但纯虚函数必须在子类实现才可以实例化子类。
  • 虚函数的类用于 “实作继承”,继承接口的同时也继承了父类的实现。纯虚函数关注的是接口的统一性,实现由子类完成。
  • 带纯虚函数的类叫抽象类,这种类不能直接生成对象,而只有被继承,并重写其虚函数后,才能使用。抽象类被继承后,子类可以继续是抽象类,也可以是普通类。
  • 虚基类是虚继承中的基类,具体见下文虚继承

CSDN . C++ 中的虚函数、纯虚函数区别和联系

虚函数指针与虚函数表

  • 虚函数指针:在含有虚函数类的对象中,指向虚函数表,在运行时确定。
  • 虚函数表:在程序只读数据段(.rodata section,见:目标文件存储结构),存放虚函数指针,如果派生类实现了基类的某个虚函数,则在虚表中覆盖原本基类的那个虚函数指针,在编译时根据类的声明创建。

C++中的虚函数(表)实现机制以及用C语言对其进行的模拟实现

  • 虚函数 C++对象的内存布局
    • 只有数据成员的对象
    • 没有虚函数的对象
    • 拥有仅一个虚函数的类对象
    • 拥有多个虚函数的类对象
    • 单继承且本身不存在虚函数的继承类的内存布局
    • 本身不存在虚函数(不严谨)但存在基类虚函数覆盖的单继承类的内存布局
    • 定义了基类没有的虚函数的单继承的类对象布局
    • 多继承且存在虚函数覆盖同时又存在自身定义的虚函数的类对象布局
    • 如果第1个直接基类没有虚函数(表)
    • What if 两个基类都没有虚函数表
    • 如果有三个基类: 虚函数表分别是有, 没有, 有!

虚继承

原因:虚继承用于解决多继承条件下的菱形继承问题(浪费存储空间、存在二义性)。
目的:令某个类做出声明,承诺愿意共享它的基类
形式:

//关键字public virtual的顺序随意
class A:public virtual B;
class C:virtual public B;
  • 底层实现原理与编译器相关,一般通过虚基类指针虚基类表实现,每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间)(需要强调的是,虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。

  • 实际上,vbptr 指的是虚基类表指针(virtual base table pointer),该指针指向了一个虚基类表(virtual table),虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持着公共基类(虚基类)的两份同样的拷贝,节省了存储空间。

C++ 虚继承实现原理(虚基类表指针与虚基类表

虚继承与虚函数

  • 相同之处:都利用了虚指针(均占用类的存储空间)和虚表(均不占用类的存储空间)
  • 不同之处:
    • 虚继承
      • 虚基类依旧存在继承类中,只占用存储空间
      • 虚基类表存储的是虚基类相对直接继承类的偏移
    • 虚函数
      • 虚函数不占用存储空间
      • 虚函数表存储的是虚函数地址

模板类、成员模板、虚函数

  • 模板类中可以使用虚函数
  • 一个类(无论是普通类还是类模板)的成员模板(本身是模板的成员函数)不能是虚函数

抽象类、接口类、聚合类

  • 抽象类:含有纯虚函数的类
  • 接口类:仅含有纯虚函数的抽象类
  • 聚合类:用户可以直接访问其成员,并且具有特殊的初始化语法形式。满足如下特点:
    • 所有成员都是 public
    • 没有定义任何构造函数
    • 没有类内初始化
    • 没有基类,也没有 virtual 函数·

继承、向上转型、向下转型时虚函数调用规则

参考:继承、向上转型、向下转型时虚函数调用规则

  • 函数调用具体看三个关键信息:内存块的类型,当前类型,以及是否为虚函数
    • 对于虚函数,主要看查找谁的虚函数表
      • 对于对象调用函数,查找当前类型的类的虚函数表
      • 对于指针类型,看内存块的具体类型,查找它的类的虚函数表’
    • 对于非虚函数,不涉及到查虚函数表的问题,直接调用当前类型的类里的函数