为什么要有虚继承?
在c++中我们有一种继承方式叫做菱形继承,通俗来说就是有两个类(son1,son2)都继承了同一个类(base),然后又有一个类(grandson)来继承这两个类(就是多继承的一种,个人不建议在实际开发中使用多继承)。
但是这样做会带来两个问题
- son1继承了base,son2也继承了base,当grandson继承son1和son2时并使用了其中的数据时,便会产生二义性。例如:base中有一个成员m_a,那么son1和son2中都有这个m_a,这个时候grandson由于继承了son1和son2,那grandson里面就会有两个m_a,当你在grandson创建的对象中使用这个m_a的时候,如果不用作用域加以修饰的话,编译器很难分清你是使用son1中的m_a还是son2中的m_a,这个时候编译器就会报错。
- 由于grandson继承了son1和son2,将代表着grandson中有着两份base的数据,这就导致了资源的浪费。
或许这样看不是很清楚,我们上代码看看
class base {
public:
int m_a;
};
class son1:public base{};
class son2:public base{};
class grandson :public son1, public son2{};
int main()
{
grandson g;
g.m_a = 10;//这里就会报错,提示m_a不明确
return 0;
}
那么c++给我们提供了虚继承用来解决这种问题,只需要在son1和son2继承base时使用虚继承即可(grandson正常继承son1和son2),语法:class son1 : virtual public base {}; class son2 : virtual public base {}; 其中base类叫作虚基类,这种继承方式就是虚继承。
class base {
public:
int m_a;
};
class son1:virtual public base{};
class son2:virtual public base{};
class grandson :public son1, public son2{};
int main()
{
grandson g;
g.m_a = 10;//这样这里就不会出现之前的不明确的情况
return 0;
}
那么为什么加上virtual将能解决上述问题呢,这个就和虚继承的底层实现有关系。
虚继承的底层实现:
在son1和son2虚继承base时,其实继承过来的是一个虚基类指针(vbptr——virtual base pointer),它指向的是一个虚基类表(vbtable——virtual base table),这个表中记录了一个数据——偏移量,当你继承这个虚基类指针时,它通过偏移量也能找到你想要的成员,所以上面的m_a只有一份,grandson中只是继承了两个不同的虚基类指针,通过这两个指针来找到这唯一的m_a,就能解决二义性以及资源浪费问题。
如果大家不是很能理解,也可以打开vs的开发人员命令行提示符来查看类的对象模型。
具体操作步骤如下:
- 打开vs的开发人员命令行提示符
- 找到你的代码文件所在的盘符并跳转到该盘符
- 然后输入cd [代码文件所在的路径]
- 跳转到文件所在位置后输入dir
- 再输入 cl /d1 reportSingleClassLayout[你想要查看的类名] [ ".cpp文件的名字" ]
接下来可以看看我的对象模型