虚继承的出现就是为了解决多继承中访问不明确的问题。
首先让我们先看一下虚继承的代码:
#include<iostream>
using namespace std;
class AA
{
public:
int m_a;
AA()
{
m_a = 100;
}
};
class BB : virtual public AA
{
public:
int m_b;
BB()
{
m_a = 200;
}
};
class CC : virtual public AA
{
public:
int m_c;
CC()
{
m_a = 300;
}
};
class DD : public BB, public CC
{
public:
int m_d;
DD()
{
m_a = 400;
}
};
int main()
{
DD dd;
cout << sizeof(dd) << endl;
cout << &dd << endl;
cout << &dd.m_a << endl;
cout << &dd.m_b << endl;
cout << &dd.m_c << endl;
cout << &dd.m_d << endl;
cout << dd.AA::m_a << endl;
cout << dd.BB::m_a << endl;
cout << dd.CC::m_a << endl;
cout << dd.m_a << endl;
system("pause");
return 0;
}
输出结果:
dd对象在内存中的结构如下:
我们看到虚继承中,内存的分配是与多继承不同的,我们不再是赋值两份AA对象,而是将虚基类放在了最后面,然后原来的BB和CC所继承的AA的地址存放的是指向AA对象的指针,这样做的另一个好处是还可以节约内存空间,因为如果AA对象的大小是100个字节,那我们也只需要一个4字节的指针指向它就可以了,而不用赋值一份儿占用空间。
注意:虚基类只有一份,并且所有继承虚基类的子类都要虚继承,否则如果有一个不是虚继承的那也会再复制一份AA,内存中同样存在了两份AA,那么访问不明确的问题就还是存在。
虚基类为什么要放在最后?
因为我们在使用这个虚基类的时候首先要知道是哪一个对象(就是指向虚基类的指针)调用的它,我们把虚基类放在最后就是要保证:前面的所有的对象使用虚基类的时候都是通过指向虚基类的指针找到的。如果我们不放在后面而是放在前面,那么找到虚基类就有了两种方式:直接使用对象的地址 or 指向虚基类的指针,这样我们在使用虚基类的时候还要加一个判断,来判断到底是谁调用的呢?这样显然更麻烦了。所以我们要把虚基类放在后面,让所有子类都是通过指向虚基类的指针来找到它。
含有虚函数的虚继承
我们首先看一道题:下列程序的结果是什么?
#include <iostream>
#include <memory.h>
#include <assert.h>
using namespace std;
class A
{
char k[3];
public:
virtual void aa(){};
};
class B : public virtual A
{
char j[3];
public:
virtual void bb(){};
};
class C : public virtual B
{
char i[3];
public:
virtual void cc(){};
};
int main(int arge,char *argv[])
{
cout<<"sizeof(A):"<<sizeof(A)<<endl;
cout<<"sizeof(B):"<<sizeof(B)<<endl;
cout<<"sizeof(C):"<<sizeof(C)<<endl;
system("pause");
return 0;
}
输出结果:
我们来看一下c对象在内存中的结构:
从图中我们可以看出:每个类最初始的4个字节都是指向虚函数列表的指针,由于A是虚基类,没有父类,所以A只有8个字节大小;而B虚继承了A,所以B要有一个指向A类地址的指针,所以B类是12+8(A的大小)=20;同样,C类虚继承了B,所以C要有一个指向B类地址的指针,所以C类是12+20(B的大小)=32。
什么情况下会产生新的虚函数列表呢?
为了便于理解,我们首先来看一下上一个例子中,改为普通继承情况下的输出:
我们现在再看一下c对象在内存中的结构:
那么我们是通过什么方式来确定他们的结构的呢?就是分别用不同类型的指针去指向c这个对象。如下:
C c;
A* a = &c;
B* b = &c;
当我们使用普通继承的时候a、b、c所返回的地址都是相同的,即 0C 地址。所以在实现多态的时候,父类将虚函数加到了这个虚表中,而他的子类在重写虚函数的时候也是首先复制了父类的虚函数列表,然后用自己重写的虚函数的指针覆盖父类所存入的指针。这样我们在父类调用多态的时候就可以直接从虚表里找到新的虚函数的地址了。