✊✊✊每日一面——虚函数、动态绑定、静态绑定
- 一、虚函数是怎么实现的?它存放在哪个内存的哪个区?什么时候生成
- 二、构造函数和析构函数为什么不可以声明为虚函数?
- 三、动态绑定和静态绑定是怎么实现的?
- 四、类中static函数和内联函数能否声明为虚函数?
一、虚函数是怎么实现的?它存放在哪个内存的哪个区?什么时候生成
在C++中,虚函数的实现原理基于两个关键概念:虚函数表和虚函数指针
- 虚函数表:每个包含虚函数的类都会生成一个虚函数表,其中存储着该类中所有的虚函数地址,虚函数表是一个由指针构成的数组,每个指针指向一个虚函数的实现代码
- 虚函数指针:在对象的内存布局中,编译器会添加一个额外的指针,称为虚函数指针或虚表指针,这个指针指向该对象对应的虚函数表,从而让程序能够动态的调用虚函数
当一个基类指针或引用调用虚函数时,编译器会使用虚表指针来查找该对象对应的虚函数表,并根据函数在虚函数表中的位置来调用正确的虚函数
在编译阶段生成,虚函数和普通函数一样存放在代码段,只是它的指针又存放在了虚表之中
二、构造函数和析构函数为什么不可以声明为虚函数?
1)在C++中,虚函数的调用是通过虚函数表来实现的。但是在对象创建的过程中,由于对象还没有完全构造成功,因此在构造函数和析构函数中不能使用虚函数,这是因为在对象构造期间,虚函数表尚未构建,而且对象还没有完成其完整的初始化。此外,构造函数的调用顺序是从基类到派生类,因为基类部分先于派生类部分构建,如果基类构造函数是虚函数,那么它将无法正常地被调用,因为调用虚函数之前必须先构造对象。同样,派生类构造函数也不能是虚函数,因为派生类的构造函数必须调用其基类的构造函数,如果基类的构造函数是虚函数,将无法保证正常的顺序。因此,类的构造函数不能是虚构函数。如果需要在对象的生命周期内支持多态性,可以使用虚析构函数来实现
2)析构函数可以被声明为虚函数,因为当基类指针指向派生类对象的时候,发生多态,如果不将基类的析构函数定义为虚函数的话,那么派生类的析构函数就无法执行,造成内存泄露
补充:
- 虚函数通过使用 virtual 关键字来声明,如果一个函数被声明为虚函数,那么当通过指向对象的指针或者引用调用该函数时,程序将会根据对象的实际类型来调用相应的函数,而不是根据这种指针或者引用的类型来调用函数
- 为了实现这一机制,C++编译器会在对象的内存布局中添加一个虚函数表(virtual table,也称 vftable)。虚函数表是一个指向虚函数的指针数组,每个虚函数在数组中对应一个条目。当调用虚函数时,程序会通过对象的虚函数表找到对应的虚函数并调用它
- 值得注意的是:只有通过使用基类对象的指针或引用调用虚函数才会触发动态绑定机制,即根据对象的实际类型来调用函数,如果直接使用普通基类对象,则会按照静态绑定的方式调用虚函数
三、动态绑定和静态绑定是怎么实现的?
- 静态绑定:变量在声明时的类型,是在编译阶段确定的,静态类型不能更改
- 动态绑定:模板所指对象的类型,是在运行阶段确定的,动态类型可以更改
四、类中static函数和内联函数能否声明为虚函数?
- 不能,因为类中的
static
函数是所有类实例化对象所共有的,没有 this 指针,而虚函数依靠 vfptr 和 vftable 来处理,vfptr 是一个指针,在类中的构造函数中生成,并且只能通过 this 指针访问,对于静态成员函数而言,由于其没有 this 指针,无法访问 vfptr,因此也无法被声明为虚函数 - 不能,
inline
内联函数在编译时会直接将函数题嵌入到函数的调用点,没有函数调用的开销,而虚函数是通过虚函数表来确定,无法进行内联