虚继承及继承的内存布局

1.为什么需要虚继承

如下图所示如果访问Der::Fun or Der::m_nValue就会带来二义性,无法确定是调用Base1的还是Base2的,所以为了解决多重继承情况下成员访问的二义性,引入了虚继承机制。
一般继承:
一般继承

虚继承:
虚继承

2.虚继承实现

在虚继承下,Der通过共享虚基类SuperBase来避免二义性,在Base1,Base2中分别保存虚基类指针,Der继承Base1,Base2,包含Base1, Base2的虚基类指针,并指向同一块内存区,这样Der便可以间接存取虚基类的成员,如下图所示:

class SuperBase
{
public:
int m_nValue;
void Fun() {}
virtual ~SuperBase() {}
};
class Base1 : virtual public SuperBase
{
public:
virtual ~Base1() {}
};
class Base2 : virtual public SuperBase
{
public:
virtual ~Base2() {}
};
class Der :public Base1, public Base2
{
public:
virtual ~Der() {}
};
int main()
{
printf("sizeof(superbase)= %d\n",sizeof(SuperBase));
// sizeof(int)+8个字节的虚表指针。= 12  64位,指针8个字节,保证是8个字节的倍数,16
printf("sizeof(Base1)= %d\n", sizeof(Base1));      
// sizeof(int)+8个字节的虚表指针。+8个字节的虚拟BASE指针= 20 指针8个字节,保证是8个字节的倍数,24
printf("sizeof(Base2)= %d\n", sizeof(Base2));
// sizeof(int)+8个字节的虚表指针。+8个字节的虚拟BASE指针= 20 指针8个字节,保证是8个字节的倍数,24
printf("sizeof(Der)= %d\n", sizeof(Der));
// sizeof(int)+8个字节的虚表指针。+8个字节的虚拟BASE指针 + 8个字节的虚拟BASE指针= 28
// 指针8个字节,保证是8个字节的倍数,32
system("pause");
return 0;

}


继承中的内存布局

1. 虚函数指针(vptr)放最前,之后放变量。
2. 多个父类排着放,再放子类
3. 子类的覆盖的虚函数将所有祖先的同名虚函数都覆盖。
4. 子类其它的虚函数指针放在第一个父类的虚函数表里。
5. 虚拟继承的情况只需要在钻石继承中有必要使用(避免二义性),子类中最先的祖先放最后。

多说不如举例,看下面转的文章。

本文章转自:http://blog.csdn.net/randyjiawenjie/article/details/6693337

分为四种情况:
1.单继承

2.多继承(不含钻石继承)

3.非虚继承的钻石继承

4.虚继承的钻石继承

注:下面所有类中的函数都是虚函数。

1.单继承

单继承体系如下:

GrandChild对象的内存布局:

可见以下几个方面:

1)虚函数表在最前面的位置。

2)成员变量根据其继承和声明顺序依次放在后面。

3)在单一的继承中,被overwrite的虚函数在虚函数表中得到了更新。

2.多继承

多继承的体系如下:

Derive对象的内存布局如下:

我们可以看到:

1) 每个父类都有自己的虚表。

2) 子类的成员函数被放到了第一个父类的表中。

3) 内存布局中,其父类布局依次按声明顺序排列。

4) 每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

出现钻石继承的虚继承的时候,虚基类在子类中只有一份。

出现钻石继承的非虚继承的时候,虚基类在每个子类中都有一份。

3.非虚继承的钻石继承

继承体系如下:

D的内存布局如下:

红色的部分就是重复的部分,就会造成二义性

4.虚继承的钻石继承

(虚继承就是解决钻石继承问题的,如果不存在钻石继承,就不用虚继承)

继承体系如下:(红色专门标准虚继承)


D的内存布局如下:

可以看出,少了重合的部分。但是,代价是增加了一个虚函数指针。


猜你喜欢

转载自blog.csdn.net/bjzhaoxiao/article/details/80237724
今日推荐