C++中的虚函数(表)实现机制以及用C语言对其进行的模拟实现(二)
2 };
2 const void** __vfptr = &__fun[0];
?
?
? ? 通过上面两张图表, 我们可以得到如下结论:
? ? ? ? 1. 更加肯定前面我们所描述的: __vfptr只是一个指针, 她指向一个函数指针数组(即: 虚函数表)
? ? ? ? 2. 增加一个虚函数, 只是简单地向该类对应的虚函数表中增加一项而已, 并不会影响到类对象的大小以及布局情况.
?
? ? 前面已经提到过: __vfptr只是一个指针, 她指向一个数组, 并且: 这个数组没有包含到类定义内部, 那么她们之间是怎样一个关系呢?
? ? 不妨, 我们再定义一个类的变量b2, 现在再来看看__vfptr的指向:
? ? ? ? ? ?
?
? ? 通过Watch 1窗口我们看到:?
? ? ? ? 1. b1和b2是类的两个变量, 理所当然, 她们的地址是不同的(见 &b1 和 &b2).
? ? ? ? 2. 虽然b1和b2是类的两个变量, 但是: 她们的__vfptr的指向却是同一个虚函数表.
?
? ? 由此我们可以总结出:
? ? ? ? ? 同一个类的不同实例共用同一份虚函数表, 她们都通过一个所谓的虚函数表指针__vfptr(定义为void**类型)指向该虚函数表.
?
? ? 是时候该展示一下类对象的内存布局情况了: ? ? ? ??
?
? ? ? ?
?
? ? 不出意外, 很清晰明了地展示出来了吧? :-) hoho~~
? ? 那么问题就来了! 这个虚函数表保存在哪里呢? 其实, 我们无需过分追究她位于哪里, 重点是:
? ? ? ? ? ? 1. 她是编译器在编译时期为我们创建好的, 只存在一份.
? ? ? ? ? ? 2. 定义类对象时, 编译器自动将类对象的__vfptr指向这个虚函数表.
?
? ? 5. 单继承且本身不存在虚函数的继承类的内存布局
?
? ? 前面研究了那么多啦, 终于该到研究继承类了! 先研究单继承!
?
? ? 依然, 简单地定义一个继承类, 如下:
?
复制代码
?1 class Base1
?2 {
?3 public:
?4 ? ? int base1_1;
?5 ? ? int base1_2;
?6?
?7 ? ? virtual void base1_fun1() {}
?8 ? ? virtual void base1_fun2() {}
?9 };
10?
11 class Derive1 : public Base1
12 {
13 public:
14 ? ? int derive1_1;
15 ? ? int derive1_2;
16 };
复制代码
? ? ?我们再来看看现在的内存布局(定义为Derive1 d1):
图1:?
?
? ? ??
?
? ? ? 没错! 基类在上边, 继承类的成员在下边依次定义! 展开来看看:
图2: ? ? ?
?
? ? ? 经展开后来看, 前面部分完全就是Base1的东西: 虚函数表指针+成员变量定义.
? ? ? 并且, Base1的虚函数表的[0][1]两项还是其本身就拥有的函数: base1_fun1() 和 base1_fun2().
?
? ? ? 现在类的布局情况应该是下面这样:
? ? ??
?
? ? 6. 本身不存在虚函数(不严谨)但存在基类虚函数覆盖的单继承类的内存布局
?
? ? ? 标题`本身不存在虚函数`的说法有些不严谨, 我的意思是说: 除经过继承而得来的基类虚函数以外, 自身没有再定义其它的虚函数.
? ? ? Ok, 既然存在基类虚函数覆盖, 那么来看看接下来的代码会产生何种影响:
?
复制代码
?1 class Base1
?2 {
?3 public:
?4 ? ? int base1_1;
?5 ? ? int base1_2;
?6?
?7 ? ? virtual void base1_fun1() {}
?8 ? ? virtual void base1_fun2() {}
?9 };
10?
11 class Derive1 : public Base1
12 {
13 public:
14 ? ? int derive1_1;
15 ? ? int derive1_2;
16?
17 ? ? // 覆盖基类函数
18 ? ? virtual void base1_fun1() {}
19 };
复制代码
?
?
? ? 可以看到, Derive1类 重写了Base1类的base1_fun1()函数, 也就是常说的虚函数覆盖. 现在是怎样布局的呢?
?
? ? ? ? ??
?
? ? 特别注意我高亮的那一行: 原本是Base1::base1_fun1(), 但由于继承类重写了基类Base1的此方法, 所以现在变成了Derive1::base1_fun1()!
? ? 那么, 无论是通过Derive1的指针还是Base1的指针来调用此方法, 调用的都将是被继承类重写后的那个方法(函数), 多态发生鸟!!!
?
? ? 那么新的布局图:
?
?
? ? 7. 定义了基类没有的虚函数的单继承的类对象布局
?
? ? ?说明一下: 由于前面一种情况只会造成覆盖基类虚函数表的指针, 所以接下来我不再同时讨论虚函数覆盖的情况.
? ? ?继续贴代码:
?
复制代码
?1 class Base1
?2 {
?3 public:
?4 ? ? int base1_1;
?5 ? ? int base1_2;
?6?
?7 ? ? virtual void base1_fun1() {}
?8 ? ? virtual void base1_fun2() {}
?9 };
10?
11 class Derive1 : public Base1
12 {
13 public:
14 ? ? int derive1_1;
15 ? ? int derive1_2;
16?
17 ? ? virtual void derive1_fun1() {}
18 };
复制代码
? ? 和第5类不同的是多了一个自身定义的虚函数. 和第6类不同的是没有基类虚函数的覆盖.
?
?
?
? ? 咦, 有没有发现问题? 表面上看来几乎和第5种情况完全一样? 为嘛呢?
? ? 现在继承类明明定义了自身的虚函数, 但不见了??
? ? 那么, 来看看类对象的大小, 以及成员偏移情况吧:
?
?
? ? 居然没有变化!!! 前面12个字节是Base1的, 有没有觉得很奇怪?
?
? ? 好吧, 既然表面上没办法了, 我们就只能从汇编入手了, 来看看调用derive1_fun1()时的代码:
?
1 Derive1 d1;
2 Derive1* pd1 = &d1;
3 pd1->derive1_fun1();
?
?
? ? 要注意: 我为什么使用指针的方式调用? 说明一下: 因为如果不使用指针调用, 虚函数调用是不会发生动态绑定的哦! 你若直接 ?d1.derive1_fun1(); , 是不可能会发生动态绑定的, 但如果使用指针: ?pd1->derive1_fun1(); , 那么 pd1就无从知道她所指向的对象到底是Derive1 还是继承于Derive1的对象, 虽然这里我们并没有对象继承于Derive1, 但是她不得不这样做