1.虚函数
不要在构造函数和析构函数中调用虚函数
下面说说原理:
假如基类有个虚函数
那么编译器会为其创建虚函数表vtbl
并在对象的内存空间创建虚函数指针vptr
虚函数表的原理是每个类会在里面有自己的所能调用到的虚函数地址
对象的内存空间一般只有两样东西:
虚函数指针vptr 和 数据成员(包括直接基类和间接基类的)
在对象初始化过程中
先构造基类对象再构造派生类对象
也就是说当前正在执行基类的构造函数时
执行完初始化列表之后 执行构造函数体之前
编译器插入了初始化vptr的逻辑
令对象的vptr指向是基类的vtbl
所以此时无论如何也访问不到派生类的vtbl
也就无法调用派生类中覆盖(overrided)了的虚函数
只有执行到派生类的构造函数时
才能更新vptr指向派生类的vtbl
此后调用的虚函数才是派生类中覆盖了的虚函数
析构函数函数是构造函数的逆过程
所以在派生类的析构函数中的vptr
基类析构函数中的vptr是不同的
同样是执行析构函数体之前就被改为了对应当前类的vptr
下面我们做个实验验证一下
struct Base {
void printVPTR() {
void (***pvptr)() = reinterpret_cast<void (***)()>(this);
void (**vptr)() = *pvptr;
printf("%p\n%p\n", this, vptr);
}
Base() {
printVPTR();}
virtual ~Base() {
printVPTR();}
};
struct Derived : Base {
Derived() {
printVPTR();}
~Derived() override {
printVPTR();}
};
int main() {
Derived d;
}
打印结果:
0x7fff5fbff748
0x1000010a8
0x7fff5fbff748
0x100001060
0x7fff5fbff748
0x100001060
0x7fff5fbff748
0x1000010a8
各位可以看到构造/析构过程中this指针是没变的
因为基类子对象和派生类子对象的区域是重叠的
都是从对象的基地址开始偏移为0的内存
如果是非第一直接基类的指针
偏移就不是0了
而是要加上在其之前的基类的成员的size
回到正题
我们看vptr的值在不同的构造函数中是不同的
基类是0x1000010a8
派生类是0x100001060
中间有7