虚函数机制
- 虚函数简单来说,就是在基类成员函数名字前面加上virtual关键字,通过派生类重写函数来实现,纯虚函数就是只有虚函数声明,没有具体定义,任何派生类都需要定义自己的实现方法。
- 特别注意的是virtual关键字只能修饰类中成员函数,不能修饰类外函数定义,纯虚函数的声明方式就是在虚函数原型后加“=0”。
- 基类中的虚函数,如果有定义,哪怕是空定义,它的作用就是让这个函数能够在子类里面被重写,这样的话,编译器后期绑定实现多态,纯虚函数只是一个接口,具体的要在子类中实现。
- 要注意的是,虚函数可以在子类中不重写,但是虚函数一定要在子类中实现才能实例化子类。虚函数的类是为了实现继承,继承接口也继承了父类的实现,纯虚函数关注的接口的统一性,实现由子类完成。
- 带有纯虚函数的类成为抽象类,这种类不能直接生成对象,可以被继承,并通过重写其虚函数才能使用,抽象函数被继承了,子类也可以是抽象函数,也可以是普通类。
- 抽象类不能为参数,抽象类的指针和引用可以作为参数,静态函数也不能为虚函数,静态函数没有this,inline 函数从理论上来说也不能是虚函数,因为inline函数没有地址,但是虚函数地址要被填入到虚表中,不过可以编译通过,inline只是建议,调用时候不构成多态可以保持inline属性,构成多态,则没有inline属性
虚函数表
多态是通过虚函数实现,而虚函数通过虚函数表实现的。具体实现是编译器在对象中新增一个隐藏成员,该隐藏成员指向函数地址数组的虚指针,虚指针位于类实例的前面,保证虚函数表具有最高的性能,这种数组成为虚函数表 vtbl ,虚函数表保存新函数的地址,如果派生类中重写虚函数,那么虚函数表中保存新的函数地址,如果派生类中没有定义虚函数,那么虚函数表中保存原始版本的地址,如果派生类中定义了新的虚函数,那么该函数地址添加了虚函数表中。
如何定位具体虚函数
编译器在编译的过程中生成虚函数表,虚函数表是一块连续的存储空间,每个元素都是指针,大小是固定的,生成后不再改变,运行期间不能新增或更改,虚函数表的构建和存取都是由编译器掌控的,在运行期,调用的虚函数地址存在虚函数表中,每个对象被安插了一个由编译器内部产生的指针,指向该表格,为了找到函数地址,每个虚函数被指派了一个表格索引值。
重载和重写
重载的定义是对函数或者运算符进行重新定义,参数的类型,顺序,个数可以不同,甚至返回类型都可以不同,但是名字相同。 重写就是派生类实现基类中的虚函数,参数名字返回值都相同,只是函数体不同。实现了多态。 派生类调用时会调用派生类的重写函数,不会调用被重写函数。 重写基类的虚函数必须有virtual 修饰。这样的函数调用者在编译期无法确定在运行时候进行绑定。
虚继承和虚基类
为什么要有虚继承
如果从不同的基类中集成了相同的函数名的时候,会出现多义性,当然可以用运算符::解决,其次如果从两个或者更多的基类继承同一个类出现多个实例。
介绍
虚基类是的从多个类派出的对象只继承一个基类对象,一般可以使用虚基类和非虚基类,这个实现原理与编译器有关。 一般通过虚基类指针和虚基类表来实现,每个虚继承的子类都有一个隐藏的虚基类指针和虚基类表,当虚继承的子类被当做父类继承时,虚基类指针也会被继承。 实际上,vbptr 指的是虚基类表指针,表中记录了虚基类与本类的偏移地址,通过偏移地址可以找到虚基类成员,虚继承不用想普通多继承那样维持着公共基类的两份同样的拷贝,节省了存储空间。 虚基类表指针总在虚函数表指针之后,一个类对象的虚基类指针指向的虚基类表,与虚函数表一样,虚基类也由多个条目组成,条目中存放的是偏移值。第一个条目存在虚基类表指针所在地址到该类对象内存首地址的偏移值。
构造函数调用顺序
- 任何虚基类的构造函数按照他们被继承的顺序构造
- 任何非基类的构造函数按照他们被构造的顺序构造
- 任何成员对象的构造按照他们的声明顺序调用
- 类自身的构造函数。