Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2697021
  • 博文数量: 877
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 5921
  • 用 户 组: 普通用户
  • 注册时间: 2013-12-05 12:25
个人简介

技术的乐趣在于分享,欢迎多多交流,多多沟通。

文章分类

全部博文(877)

文章存档

2021年(2)

2016年(20)

2015年(471)

2014年(358)

2013年(26)

分类: Windows平台

2015-05-21 19:12:03

C++对象模型简介(二)——《深度探索C++对象模型》精简笔记
http://blog.csdn.net/yang_yulei/article/details/8741547

多重继承


  1. class A  
  2. {  
  3. public:  
  4.     A() {}  
  5.     virtual ~A() {}  
  6.     virtual int foo( )  {   return  val ;  }  
  7.     virtual int funA( ) {}  
  8. private:  
  9.     int val ;  
  10.     char bit1 ;  
  11. } ;  
  12.   
  13. class B :  
  14. {  
  15. public:  
  16.     B() {}  
  17.     virtual ~B() {}  
  18.     virtual int foo( )  {   return  bit2;  }  
  19.     virtual int funB( ) {}  
  20. private:  
  21.     char bit2 ;  
  22. };  
  23.   
  24. class Derived : public A, public B  
  25. {  
  26. public:  
  27.     Derived() {}  
  28.     virtual ~Derived() {}  
  29.     virtual int foo( )  {   return  bit3;  }  
  30.     virtual int funDerived( ) {}  
  31. private:  
  32.     char bit3 ;  
  33. };  






 

 

注意:在多重继承下,若有n个基类,则派生类中有n个virtual table.

针对每一个virtual table,派生类对象中有对应的vptr。这些vptrs将在构造函数中被设立初值。

 

派生类的虚函数会覆盖(改写)其每个基类virtualtable中相应的虚函数索引值。

 

多重继承最左端的基类,在派生类中作为主要实体,其virtualtable为主要表格,其它基类的virtual table为次要表格。

当你将Derived对象地址指定给一个Base1指针或Derived指针时,被处理的virtualtable是主要表格vptr_Base1。

故主要表格要包含Derived的所有虚函数(包括继承得来的虚函数)。所以其中有Base1、Base2、Derived中的虚函数。

而其它次要表格中的项目数不变,只是有些虚函数的索引值被重写。

 

涉及多重继承的指针的转换

多重继承的问题主要发生于:派生类对象和其第二或后继的基类对象之间的转换。

【对一个多重派生对象,将其地址指定给“最左端(也就是第一个)”base class的指针,情况将和单一继承时相同,因为二者都指向相同的起始地址。需付出的成本只有地址的指定操作而已。至于第二个或后继的base class的地址指定操作,则需要将地址修改:加上(或减去)介于中间的base class subobject(s)大小】

例如:

Derived dObj ;

A* pA = &dObj ;

只需要简单地拷贝地址就行了。

 

而:

Derived* pd ;

B* pB = pd ;

需要这样的内部转化:

//虚拟C++

pB = pd ? (B*)( (char*)pd + sizeof(A) ) : 0  ;

 

 

含虚继承的多重继承

1、虚基类无数据成员

例:


  1. class A {  } ;  
  2. class B : public virtual A {  } ;  
  3. class C : public virtual A {  } ;  
  4. class D : public B, public C { };  




 

【注意】class A { };实际上并不是空的,它有一个隐晦的1字节,那是被编译器安插进去的一个char。这是为了使得class A的对象得以在内存中配置独一无二的地址。

当语言支持虚基类时,就会导致一些额外负担。在派生类中会有一个额外指针,指针指向一个相关表格,表格中存放的或者是虚基类对象,或者是其偏移量。

若虚基类为空,则表格中存放的是虚基类对象(即一个安插字节)

 

 

VC++编译器的优化:

VC++特别对 空virtual baseclass做了处理。空虚基类的派生类中只有一个4字节的指针。没有安插char,也没有字节填充。

(因为安插char的目的是为了空类实例化的对象在内存中有地址,而现在其派生类对象中已经有个指针了,其可以取地址,故不需要安插char了)

 

 

注意:如果虚基类中有数据成员,则两种编译器(“有特殊处理者”和“无特殊处理者”)就会产生完全相同的对象布局。

 

2、虚基类有数据成员

例:


  1. class A   
  2. {  
  3. public:  
  4.      …  
  5. private:  
  6.     int x, y ;  
  7. } ;  
  8.   
  9. class B : public virtual A   
  10. {  
  11. public:  
  12.     …  
  13. private:  
  14.     int valB ;  
  15. } ;  
  16.   
  17. class C : public virtual A   
  18. {  
  19. public:  
  20.     …  
  21. private:  
  22.     int valC ;  
  23. } ;  
  24.   
  25. class D : public B, public C   
  26. {  
  27. public:  
  28.     …  
  29. private:  
  30.     int valD ;  
  31. };  




 

最终的派生类对象底部是共享虚基类的部分,派生类class B、class C部分的指针指向的虚函数表的前面增加了一项:offset(从对象的开头算起,到共享虚基类部分的字节数)

这样可以快速地访问到共享虚基类的成员。

 

问:

①为什么虚继承不像一般继承那样,把基类成员放在顶部,把新增派生类成员放在尾部?

我想是:因为在最后的多重继承时,要求最终的派生类对象中只有一份基类成员,故最终派生类会从其各个父类中提取它们各自的派生数据成员。若按一般继承那样的对象模型,很容易找到父类中的派生类成员,但难以确定边界,故难以提取数据。为了提取数据方便,把基类成员放在尾部。

 

②为什么虚继承的派生类中有两个虚指针?

我想是:因为要实现多态。当基类指针指向派生类中的基类部分时,必须要有指向虚表的指针,才能实现多态。

 

注意:class D对象模型与一般多重继承的派生类对象的布局相似,多重继承最左边的基类部分的虚表是“主要表格”。故class B、class C、class A部分的虚表各不相同。它们都是为了实现多态。

 

【编程风格】一般而言,virtual base class最有效的一种运用形式是:一个抽象的virtual base class,没有任何数据成员。

阅读(569) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~