(1)基类中的构造函数调用虚函数,调用的是基类自己的虚函数
① 在编译的时候,函数调用就是静态编译的,而不是查虚函数表
② 在构造派生类时,需要先构造基类,再构造派生类,在构造基类的时候,派生类还没有构造,没有 this 指针。而虚函数的调用需要查虚函数表,需要通过 this 指针来查找
(2)基类中的析构函数调用虚函数,调用的是基类自己的虚函数
① 在编译的时候,函数调用就是静态编译的,而不是查虚函数表
② 析构的时候,先析构派生类,再析构基类,在调用基类析构函数的时候,派生类已经被析构,所以无法查虚函数表
(3)其它函数中调用虚函数,需要通过查虚函数表来调用
1 基类中调用的虚函数
如下代码,Base 是基类,其中有 3 个虚函数 Do1(),Do2() 和 Do3(),另外还有一个非虚函数 Do()。Derived 是继承 Base 的派生类,在 Derived 中覆写了 Do1(),Do2() 和 Do3()。在 main() 函数中创建了一个 Derived 对象,并把指针赋值给 Base 类型的指针 b。
使用 b 调用 Do1(),Do2() 和 Do3() 的时候,我们都知道调用的是 Derived 中的函数。使用 b 调用 Do() 的时候,函数 Do() 中调用了一个虚函数 Do1(),那么这里调用的 Do1() 是基类 Base 中的函数还是派生类 Derived 中的函数呢 ? 调用的是派生类中的 Do1()。
#include <iostream>
#include <string>
class Base {
public:
void Do() {
std::cout << "Base() Do()\n";
Do1();
}
virtual void Do1() {
std::cout << "Base() Do1()\n";
}
virtual void Do2() {
std::cout << "Base() Do2()\n";
}
virtual void Do3() {
std::cout << "Base() Do3()\n";
}
};
class Derived : public Base {
public:
virtual void Do1() {
std::cout << "Derived() Do1()\n";
}
virtual void Do2() {
std::cout << "Derived() Do2()\n";
}
virtual void Do3() {
std::cout << "Derived() Do3()\n";
}
};
int main() {
Base *b = new Derived;
b->Do();
std::cout << "----------------\n";
b->Do1();
b->Do2();
b->Do3();
return 0;
}
运行结果如下:
代码编译之后使用 objdump -d -S -l 来对 a.out 进行反汇编。
(1)调用非虚函数 Do() 的时候,在汇编代码中是通过函数符号来表示的。通过符号表硬编码,调用的是父类中的 Do()。
(2)在调用虚函数 Do1(),Do2() 和 Do3() 的时候,并不是通过符号来调用的。虚函数的调用是通过查虚表来调用的,从汇编指令中也可以看到,虚函数 Do1() 在虚函数表的第一个位置;虚函数 Do2() 在虚函数表的第二个表项,偏移 0x8;虚函数 Do3() 在虚函数表的第三个表项,偏移 0x10。
(3)在 Do() 中调用 Do1(),也是从虚函数表中找到虚函数,然后调用。
b 指向 Derived 类型的指针,其中的虚函数表是类 Derived 的,所以在 Do() 中调用的 Do1() 是 Derived 中的 Do1()。
2 基类的构造函数和析构函数调用虚函数
2.1 基类构造函数调用虚函数
如下代码 Base 是基类,Derived 是派生类。在基类 Base 的构造函数中调用虚函数 Do1(),那么调用的是基类的 Do1() 还是派生类的 Do1() 呢 ?
基类构造函数调用的虚函数是基类中的虚函数。
派生类构造函数调用的虚函数是派生类中的虚函数。
如下代码,Base 是基类,Derived 是派生类。在基类的构造函数中调用了虚函数 Do1(),在派生类的构造函数中调用了虚函数 Do1()。
#include <iostream>
#include <string>
#include <stdlib.h>
#include <stdio.h>
class Base {
public:
Base() {
std::cout << "Base()\n";
Do1();
printf("Base(), vfptr = %p\n", (unsigned long *)*(unsigned long *)(void *)this);
}
virtual void Do1() {
std::cout << "Base() Do1()\n";
}
void PrintVfptr() {
printf("Base, vfptr = %p\n", (unsigned long *)*(unsigned long *)(void *)this);
}
};
class Derived : public Base {
public:
Derived() {
std::cout << "Derived()\n";
Do1();
printf("Derived(), vfptr = %p\n", (unsigned long *)*(unsigned long *)(void *)this);
}
virtual void Do1() {
std::cout << "Derived() Do1()\n";
}
};
int main() {
Base *b = new Derived;
b->PrintVfptr();
return 0;
}
运行结果如下:
(1)从现象来看,可以看出基类的构造函数调用的虚函数是基类中实现的函数;派生类的构造函数中调用的虚函数是派生类中实现的虚函数。
(2)在基类和派生类的构造函数中打印了类的虚函数表。可以看出,在基类构造函数中的虚函数表是基类自己的虚函数表。
从下边汇编代码也可以看出来,在构造函数中调用虚函数,并没有查虚函数表,而是通过函数符号直接调用的本类的函数。
2.2 基类析构函数调用虚函数
从汇编代码中也可以看出来,基类析构函数中调用的是基类的虚函数,派生类析构函数中调用的是派生类的虚函数。
#include <iostream>
#include <string>
#include <stdlib.h>
#include <stdio.h>
class Base {
public:
Base() {
std::cout << "Base()\n";
}
virtual ~Base() {
std::cout << "~Base()\n";
Do1();
}
virtual void Do1() {
std::cout << "Base() Do1()\n";
}
};
class Derived : public Base {
public:
Derived() {
std::cout << "Derived()\n";
}
~Derived() {
std::cout << "~Derived()\n";
Do1();
}
virtual void Do1() {
std::cout << "Derived() Do1()\n";
}
};
int main() {
Base *b = new Derived;
delete b;
return 0;
}