Data 语义学(2)

四、"继承"与 Data Member

  (1). 只有继承没有多态

  先来看个例子  

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 class A
 6 {
 7 public:
 8     //
 9 private:
10     int val;
11     char c1;
12     char c2;
13     char c3;
14 };
15 
16 int main()
17 {
18     cout << sizeof(A) << endl;
19     return 0;
20 }

  输出的结果是 8 ,这个比较容易理解:

    a. val 占用 4 bytes。

    b. c1,c2,c3各占 1 byte,3 个 bytes。

    c. 边界调整(alignment)---调整到 word 需要 1 byte

  一共 8 个!那么在看下面这个例子:

  

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 class A
 6 {
 7 public:
 8     //
 9 private:
10     int val;
11     char c1;
12 };
13 
14 class B: public A
15 {
16 public:
17     //
18 private:
19     char c2;
20 };
21 
22 class C: public B
23 {
24 public:
25     //
26 private:
27     char c3;
28 };
29 
30 int main()
31 {
32     cout << sizeof(C) << endl;
33     return 0;
34 }

  这下输出结果是多少呢?可以肯定的是,C中也含有 val、c1、c2 和  c3,那么输出结果是不是也是 8呢?当然不是,是 16 !为什么呢?解释如下:

  C++ 规定出现在 derived class 中的 ”base class subobject“ 有其完整原样性。我们从下至上看看各个类在内存中的布局!

  class A 中含有两个变量 val 和 c1,其中 val 占有 4 个 bytes,c1 占有 1 个byte加起来 5 个 bytes。再加上边界调整填补 3 bte,实际会占用 8 byte。

  class B 继承了 class A 并增加了一个自己的 nonstatic data member c2,类型时 char,占有 1 byte,轻率的程序员会以为:c2 会 c1 放到一起构成 2 bytes,这样内存补齐只需补 2 bytes,class B的大小应该还是 8 bytes。然而,class B 的 c2 是被放在 class A 所填补的 3 bytes 的后面,此时 class B 的大小是 9 bytes ,在进行边界调整填补 3 bytes,实际会占用 12 bytes。同样 class C 的大小是 16 bytes ,class B 的大小 + c3 + 边界调整!下图会让你一目了然:

   图 1

  很多人会认为这种做法很愚蠢,在 class B 继承 A 的时候,将 c1 和 c2 放在一起,填补 2 bytes,在 class C 继承 class B 的时候,将 c1、c2 和 c3放到一起,填补 1 byte,这样 class A、class B、class C 的大小都是 8 bytes,比之前那样省很多空间!!!事实上是如此吗?这种做法会破坏C++中规定的 “出现在 derived class 中的 ”base class subobject“ 有其完整原样性”,会是一些代码导致致命的错误,不信你看:

  加入我们声明以下一组指针:

    B *pb;

    A *pa1,*pa2;// 这两个指针可以用来指向 class A,B,C。

  如果执行操作:

    *pa1 = *pa2;

  应该执行一个默认的“memberwise”复制操作(复制一个个的 members)对象是值得 class A 的那部分哦。如果 pa1指向一个 class B 或 class C ,那么就会将 pa2 所指的内容中 class A 的部分复制给 class B 或 class C 的 class A 的部分!如果将 A、B 和 C 的c1、c2 和 c3 捆版到一起,去填补空间,上述那些语义就没办法保留了!由于捆绑而导致的内存复制出现错误,请看下图,会更容易理解:

   

  所以知道,C++设计者们为什么宁愿浪费空间而不那么做的原因了吧?

  (2) 加上多态

  加上多态之后会使很多操作起来更有方便性,使动作在运行期决定。这使得 class 更加完美,但我们必须维持付出代价:

    a. 导入一个相关的 virtual table,用来存放他 class 所声明的每一个 virtual function 的地址。这个 table 元素的个数 = virtual function 的数目 + 1 或 2(用以支持 runtime identification)

    b. 在每一个 class object 中导入一个 vptr ,提供执行期的连接,使每个 object 能够找到相应的 virtual table。

    c. 加强 destructor,使它能够销毁那个被安插进去的 vptr。

  那这个指针到底放在哪呢?看如下代码: 

 1 class A 
 2 {
 3 public:
 4     int num;
 5     virtual void fun(){};  
 6 }
 7 
 8 class B
 9 {
10 public:
11     int num;
12     int num1;
13     virtual void fun(){}
14 }

  这时 class A 和 class B 的布局可能是这样:

   也有可能是这样   

  如果我对 class A 和 class B 进行如下该写,那么 A 和 B 的布局又会是什么样子呢?

  该写如下:

  

 1 class A 
 2 {
 3 public:
 4     int num;
 5     virtual void fun(){};  
 6 }
 7 
 8 class B: public A
 9 {
10 public:
11     int num1;
12     virtual void fun(){}
13 }

  那么 class A 和 class B 的布局会如下:

    图2 还有可能。。。另外一种就不画了,只是把vptr 移到 num 上去罢了!

  从这个图中可以发现什么?对无论继承多少次 vptr 在所有的 base class 或者 derived class 中都只有一份!并且位置都相同,但是他们(vptr)指向的 virtual table 不相同哦!都指向内存中各自的 virual table 调用时分别从各自的 virtual table 中找 virtual functions 的地址进行调用!实现了多态的机制哦!

  (3). 多重继承

  还是先来看几个类的定义:    

 1 class base1
 2 {
 3 public:
 4     // something
 5 protected:
 6     float fb1;
 7 };
 8 
 9 class dbase1: public base
10 {
11 public :
12     // something
13 protected:
14     float fdb1;
15 };
16 
17 class base2
18 {
19 public:
20     // something
21 protected:
22     float fb2;
23 };
24 
25 class Topclass :public dbase1,public base2
26 {
27 public:
28     // something
29 private:
30     float muble;
31 };

  他们的关系图如下

    图 3

  他们在内存中的布局图如下(假设他们都有虚函数哈,给他们画了 vptr 指针):

   图4

  由他们的内存布局图可知,在class Topclass 中取 class base1 和 class dbase1 的时候可以直接取到,而取class base2就必需要做相应的转化了。请看下面的例子:

    Toplass tc;

    base1   *b1;

    dbase1 *db;

    base2  *b2;

  如果是 b1 = &tc或 db = &tc都是和单继承一样的;但如果是 b2 = &tc的操作就需要相应的转化咯,如下:

    pv = ((Topclass *)((char *) &tc)) + sizeof( dbase2);

  而如果将 tc定义成 Topclass *tc;呢?那么相应的操作是否是 pv = ((Topclass *)((char *) tc)) + sizeof( dbase2);呢?只能说你这样对了一半,应该是这样:

    pv = tc ? ((Topclass *)((char *) &tc)) + sizeof( dbase2) : 0;

  因为 tc 有可能是空指针哦。至于 reference 就不会这样了,因为不可能有一个空的引用!

  (4). 虚拟继承

  //一下可能有误,回头看

  之前我们就对虚拟继承的内存比较好奇,而且在我的博文中未讲,我们只知道会生成一个指针和一个 virtual base classs table 那这些东西到底是什么样子呢?今天将详细的展示给你们看:

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 class A
 6 {
 7 public:
 8     //
 9     virtual void foo(){};
10 private:
11     int val;
12 };
13 
14 class B:  public A
15 {
16 public:
17     //virtual void foo(){}
18 private:
19 
20 };
21 
22 
23 int main()
24 {
25     cout << sizeof(A) << endl;
26     cout << sizeof(B) << endl;
27     return 0;
28 }

  以上是一个一般继承的代码,程序输出的结果大家也知道是什么了,是 8 和 8,他们内存布局图也就是我们"(2)加上多态"图 2 所画的那样。那么我稍作如下修改:

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 class A
 6 {
 7 public:
 8     //
 9     virtual void foo(){};
10 private:
11     int val;
12 };
13 
14 class B: virtual public A  //这里将继承改成了虚拟继承
15 {
16 public:
17     //virtual void foo(){}
18 private:
19 
20 };
21 
22 
23 int main()
24 {
25     cout << sizeof(A) << endl;
26     cout << sizeof(B) << endl;
27     return 0;
28 }

这个代码将输出什么呢?结果却变成了 8 和 12 啦,为什么呢?我画一下虚拟继承后的他们的内存布局:

   图 5

  你将这幅图和上面"(2)加上多态"那里画的图 2 对比一下,会发现这里会多了一个 _vptr_B 指针啦(我将上面的 _vptr 后面 加上了 _A 或 _B 以区别他们,这个没影响)。这也就是为什么上面第二个代码输出的结果是 8 和 12 了。那为什么要这么做呢?以下是我通过 vc 调试和书上所述的方式总结出来的:

  我们之前都有说过 virtual base class 是虚基类它在 derived class 中访问并不能像其他,而是另外安插了一个指针,该指针指向一个 virtual base class table 这个表放的是 virtual base class 在 derived class 中的 offset !这个指针就是上面所说的 _vptr_B。(在我的 VC 下)这个 _vptr_B 有两个作用:第一、用来指向 class B 的 virtual function table 。第二、用来指向 class B 的 virtual base class。有人说一个指针怎么会指向两个表呢?请看下图:

   图 6

  看上图明白了吧?virtual function table 和 virtual base class table 首地址是同一个。两个表合到一起了,我们就叫 virtual function table ,表的首地址在分界处,当取正索引时,取的是 VFT slots ,当取负索引时,取的是 VBC offset。这样就可以方便的访问虚基类和实现多态了!VBC offset 是指virtual base class 在 derived class中的偏移量哦!---不过大家注意了,这只是 VC 下的一个用来共享虚基类的一种方法,别的编译器可能不是采用这种方法。但是都大同小异了,都是为了能够方便的在 derived class 中访问共享的 virtual base class!

原文地址:https://www.cnblogs.com/zhuwbox/p/3426127.html