C++:钻石继承与虚继承
QUESTION:什么是钻石继承?
ANSWER:假设我们已经有了两个类Father1和Father2,他们都是类GrandFather的子类。现在又有一个新类Son,这个新类通过多继承机制对类Father1和Father2都进行了继承,此时类GrandFather、Father1、Father2和Son的继承关系是一个菱形,仿佛一个钻石,因此这种继承关系在C++中通常被称为钻石继承(或菱形继承)。
示意图:
示例:
#include<iostream>
using namespace std;
class GrandFather{ //第一层基类GrandFather
public:
GrandFather()=default;
GrandFather(int v):value(v){}
int value;
}; class Father1:public GrandFather{ //第二层基类Father1
public:
Father1()=default;
Father1(int v):GrandFather(v){}
void set_value(int value){ //设置value的值
this->value=value;
}
}; class Father2:public GrandFather{ //第二层基类Father2
public:
Father2()=default;
Father2(int v):GrandFather(v){}
int get_value(){ //获取value的值
return this->value;
}
}; class Son:public Father1,public Father2{ //第三次层类Son
public:
Son()=default;
Son(int v):Father1(v),Father2(v){}
}; int main(){
Son s();
36 s.set_value(20);
37 cout<<s.get_value()<<endl;
return ;
}
QUESTION:上例中明明将对象s的value值设置成了20,为什么最终value的输出却还是初始化值10?
ANSWER:解决这个问题我们首先需要知道对象s是如何构造的,还是上面的示例:
#include<iostream>
using namespace std;
class GrandFather{ //第一层基类GrandFather
public:
GrandFather()=default;
GrandFather(int v):value(v){
cout<<"调用了GrandFather类的构造函数"<<endl;
}
int value;
}; class Father1:public GrandFather{ //第二层基类Father1
public:
Father1()=default;
Father1(int v):GrandFather(v){
cout<<"调用Father1类的构造函数"<<endl;
}
void set_value(int v){ //设置value的值
this->value=v;
}
}; class Father2:public GrandFather{ //第二层基类Father2
public:
Father2()=default;
Father2(int v):GrandFather(v){
cout<<"调用Father2类的构造函数"<<endl;
}
int get_value(){ //获取value的值
return this->value;
}
}; class Son:public Father1,public Father2{ //第三次子类Son
public:
Son()=default;
Son(int v):Father1(v),Father2(v){
cout<<"调用Son类的构造函数"<<endl;
}
}; int main(){
Son s();
s.set_value();
cout<<s.get_value()<<endl;
return ;
}
我们发现在创建类Son的对象s时,第一层基类GrandFather的构造函数被调用了两次,这说明系统在创建对象s前会先创建两个独立的基类子对象(分别是Father1的对象和Father2的对象),然后再创建包含这两个子对象的对象s,如图:
由此可见,对象s中包含两个分属于不同子对象的成员变量value。而方法set_value()和方法get_value()虽然都是对象s的成员函数,但由于其也分属于对象s中的不同子对象,故其操作所针对的成员变量value不是同一个value,而是方法所在的子对象所包含的value,即上例中方法set_value()的功能是重新设置Father1类所创建的子对象的value值,而方法get_value()是返回Father2类所创建的子对象的value值。
int main(){
Son s();
s.set_value();
4 cout<<"Father1类创建的子对象的value值:"<<s.Father1::value<<endl;
5 cout<<"Father2类创建的子对象的value值:"<<s.Father2::value<<endl;
return ;
}
QUESTION:如何解决钻石继承中存在的“数据不一致”问题?
ANSWER:在C++中通常利用虚基类和虚继承来解决钻石继承中的“数据不一致”问题
特别注意:
1.什么是虚继承和虚基类
• 虚继承:在继承定义中包含了virtual关键字的继承关系
• 虚基类:在虚继承体系中通过关键字virtual继承而来的基类
2.为什么使用虚基类和虚继承
• 使用虚基类和虚继承可以让一个指定的基类在继承体系中将其成员数据实例共享给从该基类直接或间接派生出的其它类,即使从不同路径继承来的同名数据成员在内存中只有一个拷贝,同一个函数名也只有一个映射
#include<iostream>
using namespace std;
class GrandFather{ //第一层基类GrandFather
public:
GrandFather()=default;
GrandFather(int v):value(v){
cout<<"调用了GrandFather类的构造函数"<<endl;
}
int value;
}; class Father1:virtual public GrandFather{ //第二层基类Father1,虚继承基类GrandFather
public:
Father1()=default;
Father1(int v):GrandFather(v){
cout<<"调用Father1类的构造函数"<<endl;
}
void set_value(int value){ //设置value的值
this->value=value;
}
}; class Father2:virtual public GrandFather{ //第二层基类Father2,虚继承基类GrandFather
public:
Father2()=default;
Father2(int v):GrandFather(v){
cout<<"调用Father2类的构造函数"<<endl;
}
int get_value(){ //获取value的值
return this->value;
}
}; class Son:public Father1,public Father2{ //第三次子类Son
public:
Son()=default;
Son(int v):Father1(v),Father2(v),GrandFather(v) {
cout<<"调用Son类的构造函数"<<endl;
}
}; int main(){
Son s();
44 s.set_value(20);
45 cout<<s.get_value()<<endl;
return ;
}
上例中的钻石继承中,由于基类Father1和基类Father2采用虚继承的方式来继承类GrandFather,此时对象s中类Father1和类Father2创建的子对象共享GrandFather类创建的子对象,如图:
此时对象s中成员变量value只有一个,且被Father1类创建的子对象和Father2类创建的子对象所共享,即方法set_value()和方法get_value()操作的value是同一个成员变量。
3.构造函数的调用顺序
• 首先按照虚基类的声明顺序调用虚基类的构造函数
• 然后按照非虚基类的声明顺序调用非虚基类的构造函数
• 之后调用派生类中成员对象的构造函数
• 最后调用派生类自己的构造函数
示例:
#include<iostream>
using namespace std;
class One{
public:
int one;
One(int o):one(o){
cout<<"调用类One的构造函数"<<endl;
}
}; class Two{
public:
int two;
Two(int t):two(t){
cout<<"调用类Two的构造函数"<<endl;
}
}; class Three{
public:
int three;
Three(int t):three(t){
cout<<"调用类Three的构造函数"<<endl;
}
}; class Four{
public:
Four(){
cout<<"调用类Four的构造函数"<<endl;
}
}; class Five{
public:
int five;
Five(int f):five(f){
cout<<"调用类Five的构造函数"<<endl;
}
}; class Six:public One,virtual Two,virtual Three,public Five{
public:
Six(int value):One(value),Two(value),Three(value) ,Five(value){ //在派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用
cout<<"调用类Six的构造函数"<<endl;
}
private:
Four four;
}; int main(){
Six six();
return ;
}
4.使用虚基类和虚继承时的一些注意事项:
• 在派生类对象中,同名的虚基类只产生一个虚基类子对象,而同名的非虚基类则各产生一个非虚基类子对象
• 虚基类的子对象是由最后派生出来的类的构造函数通过调用虚基类的构造函数来初始化的。因此在派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用,如果没有列出,则表示使用该虚基类的默认构造函数。
• 虚基类并不是在声明基类时声明的,而是在声明派生类时通过指定其继承该基类的方式来声明的。
C++:钻石继承与虚继承的更多相关文章
- C++_day8_ 多重继承、钻石继承和虚继承
1.继承的复习 1.1 类型转换 编译器认为访问范围缩小是安全的. 1.2 子类的构造与析构 子类中对基类构造函数初始化只能写在初始化表里,不能写在函数体中. 阻断继承. 1.3 子类的拷贝构造与拷贝 ...
- C++对象模型:单继承,多继承,虚继承
什么是对象模型 有两个概念可以解释C++对象模型: 语言中直接支持面向对象程序设计的部分.对于各种支持的底层实现机制. 类中成员分类 数据成员分为静态和非静态,成员函数有静态非静态以及虚函数 clas ...
- C++ 多继承和虚继承的内存布局(转)
转自:http://www.oschina.net/translate/cpp-virtual-inheritance 警告. 本文有点技术难度,需要读者了解C++和一些汇编语言知识. 在本文中,我们 ...
- C++ 继承之虚继承与普通继承的内存分布
仅供互相学习,请勿喷,有观点欢迎指出~ class A { virtual void aa(){}; }; class B : public virtual A { ]; //加入一个变量是为了看清楚 ...
- C++在单继承、多继承、虚继承时,构造函数、复制构造函数、赋值操作符、析构函数的执行顺序和执行内容
一.本文目的与说明 1. 本文目的:理清在各种继承时,构造函数.复制构造函数.赋值操作符.析构函数的执行顺序和执行内容. 2. 说明:虽然复制构造函数属于构造函数的一种,有共同的地方,但是也具有一定的 ...
- 转载:C++ 多继承和虚继承的内存布局
C++ 多继承和虚继承的内存布局[已翻译100%] 英文原文:Memory Layout for Multiple and Virtual Inheritance 标签: <无> run_ ...
- C++对象模型:单继承,多继承,虚继承,菱形虚继承,及其内存布局图
C++目前使用的对象模型: 此模型下,nonstatic数据成员被置于每一个类的对象中,而static数据成员则被置于类对象之外,static和nonstatic函数也都放在类对象之外(通过函数指针指 ...
- C++ 的多继承与虚继承
C++之多继承与虚继承 1. 多继承 1.1 多继承概念 一个类有多个直接基类的继承关系称为多继承 多继承声明语法 class 派生类名 : 访问控制 基类名1, 访问控制 基类名2, ... { ...
- 2014 0416 word清楚项目黑点 输入矩阵 普通继承和虚继承 函数指针实现多态 强弱类型语言
1.word 如何清除项目黑点 选中文字区域,选择开始->样式->全部清除 2.公式编辑器输入矩阵 先输入方括号,接着选择格式->中间对齐,然后点下面红色框里的东西,组后输入数据 ...
随机推荐
- 8.1Solr API使用(分页,高亮)
转载请出自出处:http://www.cnblogs.com/hd3013779515/ 一.Solr Deep Paging(深分页) 长期以来,我们一直有一个深分页问题.如果直接跳到很靠后的页数, ...
- ES6中变量解构的用途—遍历Map结构
- python openpyxl.md
Openpyxl 创建一个工作簿 下面是创建一个工作簿.而每个工作簿至少一个工作表我们可以通过active获取正在运行的工作表. In [1]: from openpyxl import Workbo ...
- trycatche
<?phpheader("Content-type: text/html; charset=utf-8"); try{$aaa = 0;if ($aaa == 9) {ech ...
- Scrapy实践----获取天气信息
scrapy是一个非常好用的爬虫框架,它是基于Twisted开发的,Twisted又是一个异步网络框架,既然它是异步的,那么执行起来肯定会很快,所以scrapy的执行速度也不会慢的! 如果你还没没有学 ...
- WorldWind源码剖析系列:窗口定制控件类WorldWindow
在WorldWindow定制控件是从Control类派生出来的,需要自己操纵GDI+绘制所需要的界面效果,这种自定义控件比较耗费精力,需要比较深厚的GDI+和DirectX 3D开发功底.(区别于用户 ...
- Debian 鼠标左右手
环境:debian testing;xfce4桌面 在debian中想把鼠标改为左手操作,在设置中调整鼠标的按钮为左撇子根本没用!网上搜索后发现事实很简单,简单到不知该怎么说. 废话少说,放码过来. ...
- Swift图书展示项目笔记
1.Swift语言特点 Extensions(扩展):就是向一个已有的类.结构体.枚举类型或者协议类型添加新功能.这包括在没有权限获取原始源代码的情况下扩展类型的能力(即逆向建模) map: 得到一个 ...
- 使用Git进行协同开发
用了一段时间github,一直想用时间来对git的使用来做一段笔记,前段时间比较忙,现在沉下心来学习也是极好的. 很多项目开发会采用git这一优秀的分布式版本管理工具来进行项目版本管理.因为git的使 ...
- 笔记:UITextView内容垂直居中方法
- (void)contentSizeToFit { //先判断一下有没有文字(没文字就没必要设置居中了) ) { //textView的contentSize属性 CGSize contentSiz ...