我一直认为 c++ 的精髓是 虚函数。
虚函数是运行时多状的基础。
今天就来扒一扒虚函数的内部机制。
没有虚函数
我们从 内存 来看 虚函数的 底层机制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include <iostream>
class Base { public: int base_1; int base_2;
};
int main() { Base b; std::cout << sizeof(b) << '\n'; std::cout << offsetof(Base, base_1) << '\n'; std::cout << offsetof(Base, base_2) << '\n'; }
|
只有一个虚函数
接下来,我们看一下,带虚函数的对象的内存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include <iostream>
class Base { public: int base_1; int base_2; virtual void func() {} };
int main() { Base b; std::cout << sizeof(b) << '\n'; std::cout << offsetof(Base, base_1) << '\n'; std::cout << offsetof(Base, base_2) << '\n'; }
|
可以看出来,对象前面多了八个字节。让我们来看看多了的是什么。
其实我们都知道,虚函数是在一个虚函数表里面。那么我们看看对象 b 的布局。
可以看到里面有个 指针,我们把里面的值打出来,看到是 一个函数指针,指向了 func()。
新定义一个变量 b1,查看 b1 的虚函数表,和内容,可以看到跟 b 是一摸一样的。说明一个类,只有一个虚函数表。

多个虚函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <iostream>
class Base { public: int base_1; int base_2; virtual void func() {} virtual void func1() {} };
int main() { Base b; std::cout << sizeof(b) << '\n'; std::cout << offsetof(Base, base_1) << '\n'; std::cout << offsetof(Base, base_2) << '\n'; }
|
可以看到,对象对大小不变, 虚函数表里多了 func1。

单继承且本身不存在虚函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| #include <iostream>
class Base { public: int base_1; int base_2;
virtual void func() {} virtual void func1() {}
};
class Derive : public Base { public: int derive_1; int derive_2; };
int main() { Base b; Base b1; std::cout << sizeof(b) << '\n'; std::cout << offsetof(Base, base_1) << '\n'; std::cout << offsetof(Base, base_2) << '\n';
Derive d; std::cout << sizeof(b) << '\n';
}
|
直接看内存, 虚函数表的地址不一样,但是指向了相同的函数。

本身不存在虚函数但存在基类虚函数覆盖