不学网

 找回密码
 立即注册

只需一步,快速开始

手机号码,快捷登录

查看: 358|回复: 0

[c/c++] 从类的大小到虚继承(一)

[复制链接]
Smalldy 发表于 2017-12-28 16:32:32 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
本帖最后由 Smalldy 于 2017-12-28 17:06 编辑

前言
C++ 支持但是并不提倡多继承,尤其是菱形继承在实际的应用中很难遇到, 加上由于对这方面的知识未曾深入了解过,更加导致了这方面知识的“空白”。
在最近一次的面试中,考官问了相关的问题,自觉答得实在是不太好,因此也特地花时间用来研究,这里将一些东西记录下来,以便查阅。


直接进行多继承有什么问题么??
虚继承的引入必然是为了解决一些问题。设想如下的继承结构:
  1. class X{ public: int a; };
  2. class Y : public   X {};
  3. class Z : public   X {};
  4. class A : public Z, public Y {};

  5. A obja;
复制代码

此时构建出的A的对象obja应该是怎样的呢?
obja的内部会包含一个Y的对象一个Z的对象, 而Y和Z的对象则会各自包含一个X的对象。
如果我们用 [类名] 的方式表示一个对象,那么现在的情况应该是:
[A [Y [X] ] [Z [X] ] ]
如此看来, obja的内部居然出现了两个X的对象,虽然有些时刻我们需要这样的结构,但是大多数的情况下,我们是不希望这样的!
有如下情景
  1. person
  2. student 直接继承 person
  3. teacher 直接继承 person
  4. graduate 多重继承 student teacher
复制代码

person中含有姓名,年龄,地址等等一般人都有的信息, 我们绝对不希望graduate内包含两份person信息吧!这是妥妥的浪费空间。
不仅如此,除了浪费空间之外,就连访问person内的数据也变的麻烦起来,因为你要写明你是要访问从student继承来的person的信息还是要访问从taecher继承下来的oerson的信息。。。
还是最初的代码举个例子。
  1. #include <iostream>
  2. using namespace std;

  3. class X{ public : int a;  };
  4. class Y : public  X {};
  5. class Z : public  X {};
  6. class A : public Z, public Y {};


  7. int main(){
  8.     A t;        
  9.     t.Z::a = 2;//访问起来还是很繁琐的
  10.     t.Y::a = 3;  
  11.     return 0;
  12. }
复制代码

看起来这个问题还是有恼人的。如果说复杂的繁琐方式并没有引起你的反反感,那么空间的浪费就真的让人无法妥协了!


这个类型有多大?
我们先吧多重继承的事情放一放,转而初步的了解一下对象的结构,当然,对象的内存模型还是挺复杂的,所以,接下来介绍的结构仅仅适用于解释目前我们所遇到的问题,更详细的有关对象内存模型的问题,需要大家进一步的了解才能有更加深刻的认知。
猜一猜以下代码的运行结果
  1. #include <iostream>
  2. using namespace std;

  3. class X{public:int a;};
  4. class Y : public   X {};
  5. class Z : public   X {};
  6. class A : public Z, public Y {};


  7. int main(){

  8.        
  9.         int a = sizeof(X);
  10.         int b = sizeof(Y);
  11.         int c = sizeof(Z);
  12.         int d = sizeof(A);
  13.        
  14.         cout << "X = " << a << endl;
  15.         cout << "Y = " << b << endl;
  16.         cout << "Z = " << c << endl;
  17.         cout << "A = " << d << endl;
  18.        

  19.         return 0;
  20. }
复制代码


运行结果表示这四个类的大小分别是 4 4 4 8
X的大小是4
Y Z没有自己新增的东西,自然也是4
A 也没有自己新增的东西, 但是包含了 Y Z ,总数是 8
X的大小是4,意味着普通的类封装是没有额外代价或者说是额外消耗的。那么,空类的大小是否是0呢?试一试就知道了,空类的大小并不是0。而是1哦。
[size=1em]关于可空类的大小为什么是1的问题,大家可以多多搜索,看看别人的猜想,开拓一下自己的视野。
直接继承看上去比较简单,只是将父类桥套进自己的类中就可以了。因此这个简单的策略才会导致上边出现的问题,也就是 [A [Y [X] ] [Z [X] ] ] 问题。
那么,在虚继承的情况下,会发生怎样的变化呢?
  1. #include <iostream>
  2. using namespace std;

  3. class X{public:int a; };
  4. class Y : public  virtual X {};
  5. class Z : public  virtual X {};
  6. class A : public Z, public Y {};


  7. int main(){

  8.        
  9.         int a = sizeof(X);
  10.         int b = sizeof(Y);
  11.         int c = sizeof(Z);
  12.         int d = sizeof(A);
  13.        
  14.         cout << "X = " << a << endl;
  15.         cout << "Y = " << b << endl;
  16.         cout << "Z = " << c << endl;
  17.         cout << "A = " << d << endl;
  18.        

  19.         return 0;
  20. }
复制代码

输出结果显示他们的大小分别是 4 8 8 12
此处留下一个悬念,待我们了解了虚继承的机制,再来分析这结果来验证我们的理论。


临时的小结
到目前我们看看我们掌握了什么信息?
1. 知道了直接继承对象的内存布局(有助于你理解问题所在)
2. 知道了虚继承要去解决怎样的问题(有助于你发现问题)
3. 知道了虚继承之后的现象是怎样的(有助于你解决问题)

其实到现在我们足以应付问题了,出现了共同的基类有两份拷贝的问题,知道使用虚继承解决问题,并且知道使用了虚基类之后应如何正确的访问想要的成员。。。。
如果你不仅仅满足于此,还想要进一步了解虚继承到底做了什么,将C++这个黑盒子再稍微打开那么一点点的话,就随我继续进行下去吧。

在下一篇 从类的大小到虚继承(二) 你会了解到虚继承实现原理以及使用类的大小与上述理论的相互印证


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|不学网

GMT+8, 2018-9-19 05:33

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表