什么是多态?
多态顾名思义就是多种形态,具体来说就是去完成某种行为,当不同的对象去完成时产生不同的状态
如何实现多态?
实现多态的方法有:虚函数覆盖、抽象类、模板
其中虚函数用的最多,虚函数是实现多态的基石
虚函数和抽象类实现多态的三大步骤:
1.存在继承关系
2.子类重写父类的virtual function
3.子类以父类的指针或者是引用的身份出现
模板特例化也可以实现,当同时匹配上特例化和泛化的版本时,特例化的优先级更高所以可以实现多态的效果。
虚函数实现的原理 多态实现的原理
实现虚函数的关键两点为:虚函数表指针(vptr)和虚函数表(vftable)
1.虚函数指针
虚函数指针存在虚函数表内,虚函数在内存中存储在代码段。
2.虚函数表指针
如果类中存在虚函数,在编译期间编译器会在类的构造函数中安插虚表指针的赋值语句
A()
{
vptr = &A::vftable;//编译器做的,具体类中不会写出来
}
在有虚函数的类实例化一个对象后,对象在内存中的布局中,编译器会插入一个虚表指针vptr,并且虚表指针在对象内存开时的位置,所以虚表指针存在对象的内存空间里。
3.虚函数表
虚函数表是在编译的时候产生的,也就是上述操作中编译器在编译构造函数时为vptr赋值的时候创建的,在Windows中存在常量段,在Linux/Unix中存在只读数据段(.rdate);
当一个类有多个对象时,多个对象虚表指针不同但都共用一个虚函数表;
派生类和基类的虚函数表是相互独立的,当派生类有多个基类时,有几个基类中有虚函数,那么派生类中就有多少个虚表指针,也就有多少个虚函数表,此时虚表指针在对象内存中的顺序按照继承的顺序排列。
其他知识点:
多态中析构函数最好设置成虚函数,否则容易造成内存泄漏。构造函数不能设置成虚函数,因为虚函数需要虚表指针找到虚函数表中虚函数存储的地址才能被调用,但虚表指针和虚表要在构造函数中创建,所以如果将构造函数设置为虚函数实例化时会出错。
在构造函数中最好不要调用虚函数,因为派生类在运行构造函数之前会先运行基类构造函数,基类构造函数中如果调用了虚函数,此时派生类没有运行构造函数,没有初始化虚表,所以会调用基类中的没有重写的虚函数。
虚函数对于正确的基类与子类的指针转换至关重要,在类指针转换中,基类指针转为子类指针是不安全的,越界访问会造成代码运行隐患,但static_cast不做类的运行类型检查,正确的类指针转换中要使用dynamic_cast,而dynamic_cast的底层RTTI是靠虚函数表来识类对象信息的。
当不想某个类被继承时,可以用final关键字,也可以定义一个构造函数被私有的类记为A作为限制继承的工具,不想被继承的类记为B类,将B类设置为工具类A的友元类并且虚继承该工具类A,此时B类能正常创建对象,但是B类无法被继承,因为B类是A类的友元类能正常访问构造函数,但由于虚继承的派生类必须先访问祖父类的构造函数而祖父类的构造函数已经私有无法访问,所以B类无法被访问。
派生类继承的基类中有纯虚函数就必须在派生类中重写,但虚函数可以不用重写。