虚表
虚表是和类对应的,即一个类共享一个虚表。虚表指针是和对象对应的,即每个对象有自己的虚表指针。虚表中存储的是对应虚函数的地址,并不会存储非虚函数。如果继承类并没有重写父类的虚函数那么子类的虚函数表中对应的虚函数将装填父类的虚函数的地址。关于虚表中的内容的装填时机。虚表是在构造函数中完成相关项的装填的。在一个派生类构造的时候会首先调用基类的构造函数,基类就会将相应的虚函数的地址装填到到虚表里,再执行派生类的构造函数的时候派生类就会改写相应的虚表中的项。关于虚表的大小,由于虚表中的项在编译时期就可以确定(项的个数等于虚函数的个数)所以虚表不会存在堆里。至于虚表的内存布局如果派生类没有覆写对应虚函数父类的虚函数会在虚表的前面然后是派生类的虚函数地址。
虚表指针
关于虚表指针存放的问题,由于并不是C++标准规范的。并没有约束其实现,但是msvc和gcc都是存在类的最前面。PS: 我看有的面试还问为什么放在头这个我没有研究。。。。
多继承时的虚表
C++作为为数不多允许多继承的语言,使用虚表实现多态的方式在多继承的时候的表现形式值得探究一下。如果派生类继承了多个父类则会存在多个虚表。如果发生了重写则会替代所有父类虚表中的项。如果子类增加了新的虚函数则会添加在第一个虚表的后面。
常见面试题
1. 构造函数可不可以是虚函数?
不可以,我们知道装填虚函数表的时机是在构造函数里,如果构造函数也是个虚函数就是先有鸡还是先有蛋的问题了。
2. 为什么要把析构函数写成虚函数?
回答这个问题首先要知道构造函数和析构函数的执行顺序。在派生类里会先调用基类的构造函数然后调用派生类的构造函数。而在析构函数里则是相反的。会首先调用派生类的析构函数然后调用调用基类的析构函数。如果不将析构函数设置为虚函数的话,在多态的时候(即使用基类指针指向派生类对象),由于析构函数不是虚函数则只会调用基类的析构函数。而派生类的析构函数则不会被调用到从而产生内存泄漏。
3. 什么函数不可以是虚函数
1. 普通函数,没有加virtual只能重载不能重写。在编译器就会绑定
2. static静态函数,静态函数是针对类的而不是对象的
3. 友元函数。C++语言不支持友元函数的继承,对于没有继承特性的函数没有虚函数的说法。友元函数不属于类的成员函数,不能被继承。所以,友元函数不能是虚函数。
4. 构造函数。见上文
5.内联函数。因为内联函数本身就是为了在代码中直接展开,减少函数调用花费的代价而设立的,而虚函数是为了在继承后对象能够准确地执行自己的动作,这是不可能统一的。再说,inline函数在编译时被展开,虚函数在运行时才能动态地绑定函数。