您的位置:首页 > 编程语言 > C语言/C++

C++虚表撞上虚继承后的内存布局

2017-04-24 14:12 323 查看
先看一个有虚表的内存布局:

class  K
{
double a;
virtual void fun();
};
class L : public K
{
int b;
virtual void fun();
};//24
/*
+---
| +--- (base class K)
0      | | {vfptr}
8      | | a
| +---
16      | b
| <alignment member> (size=4)
+---
*/


这个没啥说的应该都会,注意字节对齐就OK

再来看看加上虚继承呢?

class  A
{
virtual void fun();
};
class B :virtual public A
{
int b;
virtual void fun();
};//12
/*
+---
0      | {vbptr}
4      | b
+---
+--- (virtual base A)
8      | {vfptr}
+---
*/


在这里就纠正一下有些人的默认思维,总以为有了虚表,vfptr总是放在这个类开始地址处,从上面我就能看见并不是这样。

你可能会说:vfptr 的优先级是不是没有 vbptr高啊?

其实并不是这样 vfptr的优先级是比vbptr高的,只是有了虚继承,自己类的东西在内存上会放在最上面,而父类的东西就放在下面了,这样做的好处是啥?就是为了不重复公共父类的成员变量。

注意:这里B类没有自己的vfptr,和父类共用一个vfptr

再来看一个菱形继承内存布局:

class  O
{
int a;
};
class P : virtual public O
{
int a;
};
class R : virtual public O
{
int a;
};
class S : public P, public R
{
int a;
};//24
/*
+---
| +--- (base class P)
0      | | {vbptr}
4      | | a
| +---
| +--- (base class R)
8      | | {vbptr}
12      | | a
| +---
16      | a
+---
+--- (virtual base O)
20      | a     这就是公共父类,在子类中只有一份,节约了内存
+---
*/


class  O
{
int a;
};
class P : virtual public O
{
int a;
};
class R : virtual public O
{
int a;
};
class S :virtual public P, virtual public R
{
int a;
};//24
/*
+---
0      | {vbptr}    //如果子类也是虚继承那么每个类都是并行的关系
8      | a
+---
+--- (virtual base O)
16      | a
+---
+--- (virtual base P)
24      | {vbptr}
32      | a
+---
+--- (virtual base R)
40      | {vbptr}
48      | a
+---
*/


然后验证我说的vfptr优先级比vbptr高:

class  G
{
int a;
virtual void fun();
};
class H : virtual public G
{
int b;
virtual void fun22();
};
/*
+---
0      | {vfptr}                   //自己的虚表
4      | {vbptr}
8      | b
+---
+--- (virtual base G)
12      | {vfptr}                   //父类的虚表
16      | a
+---
*/


是不是vfptr优先级比vbptr高啊?在这种情况下虚表指针还是处于类的前四个字节。

为什么会出现这样的情况呢?虚继承需要有自己的虚表,当自己类中出现和父类不一样的虚函数就需要创建一个属于自己的虚表,并且还要继承父类的虚表。这方面也能说明一个类的虚表可以有多个。而且是有和父类不一样的虚函数才产生的。

有人会问为啥要两个虚表?一个不行么?

再看两个例子

class  C
{
int a;
};
class D :public C
{
int b;
};
class E :public D
{
int c;
};
/*
+---
| +--- (base class D)
| | +--- (base class C)
0      | | | {vfptr}
4      | | | a
| | +---
8      | | b
| +---
12      | c
+---
*/


这个是普通继承子类是包含于父类的,当然父类的vfptr我们可以利用,没必要两个vfptr

but:

class  C
{
public:
int a;
};
class D :virtual public C
{
public:
int b;
};
class E :virtual public D
{
public:
int c;
};
/*
+---
0      | {vbptr}
8      | c
+---
+--- (virtual base C)
16      | a
+---
+--- (virtual base D)
24      | {vbptr}
32      | b
| <alignment member> (size=4)
| <alignment member> (size=4)
+---
*/


如果是虚继承,子类虚继承的父类和自己是并列的关系,并不是包含。由于是并列关系,所以vfptr可能有多个。

为啥不是嵌套类型?就要说到虚继承是如何节约内存的啦。就是为了公共父类不被重复创建(想想菱形继承),所以才每个类占一个块,这样就不可能在子类中产生两个一样的父类了。

那你可能会说为啥不共用一个呢?

我的理解是:这里的确没有节省内存,但是虚继承的公共父类成员变量节省了内存啊,并列和包含关系只能存在一种啊,虚继承用了并列,肯定也有一点点瑕疵嘛(或许我的水平还达不到,占时察觉不到具体是为什么,如果你有你的见解,希望你能给我留言)

可能大家还有个疑问,这两个虚表指针里面存放的是什么东西?

父类的虚函数是存放在第一个虚表里还是第二个虚表里?

当我们调用父类的虚函数时,用的是子类的第一个vfptr还是第二个vfptr?

#include<iostream>
#include<fstream>
using namespace std;

class  X
{
public:
virtual void test(){
cout << "test X" << endl;
}
};
class Y :virtual public X
{
public:
/*virtual void test(){
cout << "test Y" << endl;
}*/
virtual void test2(){
cout << "test2 Y" << endl;
}
};

int main(){
Y *x = new Y;
typedef void(*P)();
((P)(*(int *)*(int *)(x)))();
((P)(*(int *)*( (int *)(x) + 2 )))();
system("pause");
}
/*
test2 Y //这里可以看出来 第一个 vfptr 里面放的是不同于父类的虚函数
test X  //这里可以看出来 第二个 vfptr 里面放的是父类同名的虚函数
//如果上面代码去掉注释  第二个打印的就是 test Y
*/


typedef void (*p)() 如果看不懂请看我另一篇blog http://blog.csdn.net/program_anywhere/article/details/53040756

大东辛苦创作O(∩_∩)O哈哈~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息