先上代码:
#include <iostream>
using namespace std;
class Base
{
public:
int age = 10;
};
class Son1 : public Base
{
};
class Son2 : public Base
{
};
class Grandson : public Son1, public Son2
{
};
int main()
{
Grandson g;
//g.age = 40; //报错,模糊调用
g.Son1::age = 20;
g.Son2::age = 30;
cout << "g.Son1::age = " << g.Son1::age << endl;
cout << "g.Son2::age = " << g.Son2::age << endl;
system("pause");
return 0;
}
首先,说一下什么是菱形继承。如上代码,两个类Son1和Son2都继承自Base,之后又创建了一个Grandson类,其有两个父类,都是继承自Base的Son1和Son2类。这就构成了菱形继承。
注: Son1和Son2下面简称父类,Base简称基类,Grandson简称子类。
然后,出现了两个问题
- main函数中注释行出现了错误,由于子类的两个父类都继承自Base,所以直接写 g.age=0; 会出现指向不明的错误,即二义性,因为两个父类中都有age这个参数,编译器不知道应该访问哪一个。
解决方法 :
访问属性的时候添加作用域。如: g.Son1::age = 20; g.Son2::age = 20; 。这样编译器就知道应该访问哪一个属性了。
- 一个子类对象中会出现两个age属性。但是,事实上一个age属性就已经足够了。出翔=现两个并没有什么实际用途,还会该访问带来麻烦,也浪费内存。
解决方法 :
虚继承。虚继承就是在父类继承基类的时候,在继承方式前加上 virtual 关键字。这样就可以解决该问题。程序如下:
#include <iostream>
using namespace std;
class Base
{
public:
int age = 10;
};
class Son1 : virtual public Base
{
};
class Son2 : virtual public Base
{
};
class Grandson : public Son1, public Son2
{
};
int main()
{
Grandson g;
g.age = 40;
g.Son1::age = 20;
g.Son2::age = 30;
cout << "g.age = " << g.age << endl;
cout << "g.Son1::age = " << g.Son1::age << endl;
cout << "g.Son2::age = " << g.Son2::age << endl;
system("pause");
return 0;
}
此时在子类的实例化对象g中就只有一个age属性了,所以也就可以直接使用 g.age 进行访问和修改了。运行结果如下:
由于仅有一个age属性,所以age的值是最后由最后的赋值语句 ==g.Son2::age = 30;==赋予的30。
然后,再看一下占用内存的问题。
我们通过vs2015开发人员命令提示符来查看一下Grandson类的内存布局:
在此之前,可以在看一下虚继承之前的内存布局:
接下来是虚继承之后的:
可以看到,子类的两个父类中并没有age属性,整个类中,有且仅有一个来自Base类的age属性。在两个父类中将各有一个名为 vbptr 的东西代替了属性age,其实这是一个指针,叫做 虚基类指针 。
每个虚基类指针都给各自指向了 vbtable 虚基类表。这个虚基类表中存储了虚继承的属性列表在内存中的偏移量。而偏移量的起始位置为对象的vbptr在内存中的位置。
将代码修改为如下模式:
#include <iostream>
using namespace std;
class Base
{
public:
int age = 10;
//string name = "Base";
int high = 170;
};
class Son1 : virtual public Base
{
public:
int A = 30;
};
class Son2 : virtual public Base
{
};
class Grandson : public Son1, public Son2
{
public:
int height = 60;
};
int main()
{
Grandson g;
system("pause");
return 0;
}
再次查看内存布局结果如下:
得出如下结论:
- 只有虚继承的属性才会存储在虚基类表中。因为Son1类中的属性A,不在虚基类表中。
- 由于虚基类表的存在,无论虚继承了多少属性,在每个父类中只占4个字节的内存,即一个vbptr指针所占的空间。
- 虚继承之后虚继承的属性在内存中有且仅有一份。
- 虚基类表并不占用类的内存.