1.为什么需要虚继承?
虚继承是为了解决多重继承中存在的一些问题而出现的。在多重继承中,可能会存在多个派生类继承同一个基类的情况(菱形继承),这个时候,不仅会浪费存储空间,而且还会导致二义性。二义性解释如下:
//diamond_Inherit.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Animal
{
public:
int age;
};
//普通继承
class Sheep: public Animal
{
public:
};
class Tuo: public Animal
{
public:
};
class SheepTuo :public Sheep, public Tuo
{
public:
};
//diamond_Inherit.cpp
#include"diamond_Inherit.h"
#include <iostream>
#include <iostream>
using namespace std;
void test01()
{
SheepTuo yt;
yt.Sheep::age = 10;
yt.Tuo::age = 20;
cout << yt.Sheep::age << endl;
cout << yt.Tuo::age << endl;
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
这个时候Sheep类继承了一个age,Tuo类也继承了一个age。他们都各自保有了一份age拷贝,就会导致age不同。
2.虚继承如何实现?
先更改代码,在继承的类前加上virtual:
//diamond_Inherit.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Animal
{
public:
int age;
};
//这里添加的virtual对于Sheep和Tuo本身没影响,只对他们的子类有影响。
class Sheep: virtual public Animal
{
public:
};
class Tuo: virtual public Animal
{
public:
};
class SheepTuo :public Sheep, public Tuo
{
public:
};
要探究虚继承如何实现,需要借用VS的开发人员命令提示工具,在VS2019的工具->命令行->开发者命令提示中。
cd到当前项目的目录,输入cl /d1reportSingleClassLayout"要查看的类名" “文件名”,在这里就是cl /d1reportSingleClassLayoutSheepTuo diamond_Inherit.cpp。可以看到当前类内存的结构。(编译后才能查看到内存分布)。
这个图就是内存结构,可以看到,SheepTuo类中分别继承了来自Sheep类的vbptr(虚基类指针)和Tuo类的vbptr(虚基类指针)。这个虚基类指针指向的是一个虚基类表,可以在图中看到虚基类表中第一项存储的是vbptr与本类的偏移地址,也就是继承过来的Sheep类中初始位置就是存放Sheep类的的vbptr,在这里为0;第二项是本类的vbptr与虚基类的公有成员之间的偏移量,也就是Sheep的vbptr和Animal类的age之间偏移为8,Tuo的vbptr和age之间偏移量为4。对于虚基类的派生类,虚基类的偏移量由实际类型决定,因此在运行时才可以确定虚基类的地址。
指的注意的是,Sheep类中也是存放了一份age,在这里还可以看到,Sheep和Tuo的Size都是8,因为除了继承的age以外,还有Size为4的虚函数指针。
因为class SheepTuo :public Sheep, public Tuo
继承的时候,把Sheep和Tuo的vbptr都继承了,然后通过他们类距离虚基类中的公共成员age的偏移量发现他们指向的是同一个age,所以就不会拷贝两份,SheepTuo只保留一份age。至于虚继承底层实现原理则与编译器相关。
3.虚继承和虚函数的关系?
答:没啥关系。
至于共同点就是都利用了虚指针和虚表。
虚基类依旧存在于他的派生类中,要占用存储空间;虚函数不占用存储空间。
虚基类表存储的是虚基类相对于其派生类的偏移;而虚函数表存储的是该类中所有的虚函数对应的指针。