C++中RTTI与dynamic_cast
来源:互联网 发布:游戏视频录制软件 编辑:程序博客网 时间:2024/06/05 00:49
前言:
最近看了好些个关于C/C++类的书籍,其中几乎每本都会提到C++的类型强转,然后每本书讲的深浅不一,其中《编写高质量C/C++代码的 150个建议》其中有一段对dynamic_cast的举例说明中出现了陈述错误,可能是作者的失误,将static_cast和dynamic_cast的情况说反了,但是这让原本以为对dynamic_cast还算了解我,瞬间懵逼,难道我原来是记错了? 遂百度求证之,结果证明我还是对的,哈哈,然后再林锐的《高质量C++/C编程指南》第二版一书中,对类型转换进行了更深入的讲解,而且此书中对RTTI的讲解也是让我茅塞顿开,下面我就细细道来!
<一>C++的显示类型转换
这个首先,我觉得我们还是得从类型转换讲起;
(既然有显示那么也是有隐式的,不过这里不对隐式转换赘述;)
1.1>四种类型转换运算符
static_cast
static_cast的转换格式:static_cast (expression)
将expression转换为type-id类型,主要用于非多态类型之间的转换,不
提供运行时的检查来确保转换的安全性。主要在以下几种场合中使用:
1.用于类层次结构中,基类和子类之间指针和引用的转换;
当进行上行转换,也就是把子类的指针或引用转换成父类表示,这种转换是安全的;当进行下行转换,也就是把父类的指针或引用转换成子类表示,这种转换是不安全的,也需要程序员来保证;
2.用于基本数据类型之间的转换,如把int转换成char,把int转换成enum等等,这种转换的安全性需要程序员来保证;
3.把void指针转换成目标类型的指针,是及其不安全的;
注:static_cast不能转换掉expression的const、volatile和__unaligned属性。
dynamic_cast
dynamic_cast的转换格式:dynamic_cast (expression)
将expression转换为type-id类型,type-id必须是类的指针、类的引用或者是void *;如果type-id是指针类型,那么expression也必须是一个指针;如果type-id是一个引用,那么expression也必须是一个引用。
dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。在多态类型之间的转换主要使用dynamic_cast,因为类型提供了运行时信息;
《引自百度百科》
const_cast :去除对象的const和volatile属性;
reinterpret_cast:允许将任何指针类型转换为其它的指针类型(慎用!!!)
const_cast和reinterpret_cast 不做赘述~
<二> 细说dynamic_cast
2.1 多态
多态这个真的是C++最难理解的内容之一了;我不敢保证我可以把多态讲清楚,但是这里又不得不提多态,dynamic_cast 只适用于含有虚函数的类之间的转换,所以,只取所需部分进行说明:
正如我们所知道的,多态的实现依赖于虚函数和继承(当然,这里是动态多态,这里不提静态多态,函数多态,宏多态之类的东西),更本质的说就是依赖于虚表指针,一般情况下,虚表指针(_vptr)都位于对象的首地址,指向一个虚表(_table),而虚表中的内容则是虚函数的地址;
利用一个接口多种实现实现多态,对应到软件工程里就是,一个变点,多个变体;有些扯远了;
好了,我们暂时只需要知道这些,因为danamic_cast需要用到这些;
(如果觉得自己忘掉了多态,那就去看书吧!!)
2.2 dynamic_cast实例解说
前面已经对dynamic_cast的概念进行了陈列,相信已经帮你回忆好了,那么,下面我们就要对dynamic_cast进行举例说明,更深一步去了解danamic_cast的实现原理!
我们都知道继承的机制是基类的指针(引用)可以指向派生类的指着或者(引用),反之则编译错误;
那么:
//现在我们有这样两个类class Base{public: virtual void show(){}};class Derive:public Base{public: virtual void show(){}};
//测试
情景一:
Base* bptr = new Derive(); //Derive* dptr = bptr; //error Derive* dptr = dynamic_cast<Derive*>(bptr); //没问题 //dptr != NULL
情景二:
Base* bptr = new Base(); //Derive* dptr = bptr; //error编译不过 //编译可以过去,但是我们来看看dptr的值 Derive* dptr = dynamic_cast<Derive*>(bptr); cout<<"dptr = "<<dptr<<endl; //dptr == 0000000;
2.2.1 从情景一进行分析
我们企图用一个基类的指针去赋值一个派生类的指针,注意,基类指针的初始化是用派生类对象指针初始化的,直接进行赋值的话,编译器会直接报错,但是通过dynamic_cast的强制转化就成功了,且dptr != NULL,这是为什么呢?
2.2.2 再从情景二分析
我们企图用一个基类的指针去赋值一个派生类的指针,注意,基类指针的初始化是用基类对象指针初始化的,直接进行赋值的话,编译器会直接报错,但是通过dynamic_cast的强制转化就成功了,但是dptr == NULL,这是为什么呢?
2.2.3 综合分析
其实,如果你仔细看dynamic_cast的定义的话,肯定明白,这就是dynamic_cast的本身的机制,(有兴趣的同学可以去尝试用static_cast测试一下,看看和dynamic_cast有什么不同)这就是dynamic_cast的奇特之处,它可以办到虚函数不能做到的事,而我先现在就是要去探索这个机制的实现原理,这就引出了本篇文章的重头戏RTTI(运行时类型检测的机制);
在侯捷的《探索C++对象模型》和 林锐的《高质量C++/C编程指南》都对RTTI有比较详细的讲述,讲的内容大致相同;
<三> RTTI(Run-time Type Identification)
RTTI的起源是因为异常处理机制,多余的我就不说了,重点就是因为种原因种种原因,它需要一个运行时检查对象类型的手段,那么RTTI就诞生了!
RTTI就是为每个类型增加一个type_info对象;
如果是虚拟机制(即存在虚函数),则type_info对象的指针就放在了vtable中的第一个槽位;
注意,上面所述的内存分布大致如下。可能因为编译器的不同会有差异!
3.1.1
上述的理论都是两本书上那么讲的,但是我在我的编译器上进行测试,虚表的第一个槽位就是第一个虚函数的地址,可能是编译器的优化;我还没有明确的在我编译器上找出type_info的指针;(vs2012)
同时,探索C++对象模型这本书上给出的测试可以看出,type_info对象的地址放在虚表的前一个空间:
Aclass* ptra=new Bclass; int ** ptrvf=(int**)(ptra); RTTICompleteObjectLocator str= *((RTTICompleteObjectLocator*)(*((int*)ptrvf[0]-1)));
另外,文章typeid详解中提到:
ISO C++标准并没有确切定义type_info,它的确切定义编译器相关的,但是标准却规定了其实现必需提供如下四种操作(在之后的章节中我会来分析type_info类文件的源码):
t1 == t2 :如果两个对象t1和t2类型相同,则返回true;否则返回false
t1 != t2 :如果两个对象t1和t2类型不同,则返回true;否则返回false
t.name() : 返回类型的C-style字符串,类型名字用系统相关的方法产生
t1.before(t2) : 返回指出t1是否出现在t2之前的bool值
3.1.2 RTTI 和 dynamic_cast的关系
注:(RTTI和虚函数的动态决议不是一回事)林锐:316页;
为了能够在运行时获取对象类型的type_info信息,C++增加了两个运算符:typeid 和 dynamic_cast 两个运算符;
typeid的作用是返回一个type_info对象的引用,typyid我们在平时的编程中也都用到过,想要详细了解type_info的同学可以去看看typeinfo的内部实现,我们平时想要看一个对象的类型,可以用typeid().name()来查看,如果是非多态对象或者是基本数据类型,就会返回静态类型所对应的type_info对像;
真正奇特的地方在于,对于存在继承关系的对象,也就是为了支持dynamic_cast运算符,RTTI必须维护一棵继承树,来确认是否存在is-a关系,这就联系到了dynamic_cast运算符的使用时,如果存在is-a关系时可以正常转换的原因,而更关键的是,dynamic_cast比较的对象的真正类型,而这个真正的类型就是通过访问 vtable 来获取 type_info的,所以,真正支持dynamic_cast运算符的是RTTI;
通过上面讲的这么一大堆,我们现在就可以重新审视前面的情景一和情景二的具体实现过程了;
3.1.3 情景一,二再分析
情景一:
Base* bptr = new Derive(); //typeid(*bptr).name() 结果:class Derive cout<<typeid(*bptr).name()<<endl; //Derive* dptr = bptr; //error Derive* dptr = dynamic_cast<Derive*>(bptr); //没问题 //typeid(*bptr).name() 结果:class Derive //dptr != NULL
情景二:
Base* bptr = new Base(); cout<<typeid(*bptr).name()<<endl; //结果:class Base; //Derive* dptr = bptr; //error编译不过 //编译可以过去,但是我们来看看dptr的值 Derive* dptr = dynamic_cast<Derive*>(bptr); //cout<<typeid(*dptr).name()<<endl; //程序会挂掉; cout<<"dptr = "<<dptr<<endl; //dptr == 0000000;
dynamic_cast 通过RTTI访问虚表里的type_info,再根据type_info和Deriver进行继承树的遍历判断是否有is-a关系,若有,则返回Deriver对象的引用或指针,否则返回NULL;
利用typeid可以得到对象的确切类型,注意typeid()里的参数是对象,是对dptr进行了解引用,如果是指针的话,返回的是指针的类型,得不到确切的结果的;
这就很好的解释了dynamic_cast和RTTI的关系,并且了解其实现原理!
其实STL里的value_type也用了相似的原理,以后深入学习之后会进行补充!
注:其中很多不完善的地方,以后继续完善!
附RTTI的百度百科,很详细:
http://baike.baidu.com/link?url=b2M8dwztTkzS1Hm9X0rc5-ek8UKvJb4LjOg10FVkwFbYRKAfSEB1e1SPjlCWjtEI_xhc3VipuZWDPcVz4ZVttK
- C++中RTTI与dynamic_cast
- RTTI(dynamic_cast与typeid)
- RTTI中dynamic_cast和typeid
- C++中的RTTI与dynamic_cast<> static_cast<>
- C++的RTTI(dynamic_cast与typeid)
- 浅议 Dynamic_cast 和 RTTI与虚函数表的关系
- 动态类型识别RTTI与Dynamic_cast运算符
- 浅议 Dynamic_cast 和 RTTI
- 浅议 Dynamic_cast 和 RTTI
- 浅议 Dynamic_cast 和 RTTI
- 浅议 Dynamic_cast 和 RTTI
- 浅议 Dynamic_cast 和 RTTI
- 浅议 Dynamic_cast 和 RTTI
- 浅议 Dynamic_cast 和 RTTI
- 浅议 Dynamic_cast 和 RTTI
- 浅议 Dynamic_cast 和 RTTI
- 浅议 Dynamic_cast 和 RTTI
- 浅议 Dynamic_cast 和 RTTI
- [Java面试十二]数据库概念相关
- 字符串方法总结
- 兄弟连HTML5——09.canvas实例2——太阳系2.html
- 文件综合应用-配置文件的读写修改
- [Mac OS X] 如何在终端查看 Mac OS 版本信息
- C++中RTTI与dynamic_cast
- [Java面试三]JavaWeb基础知识总结.
- JavaScript childNodes attributes
- POJ 1511 Invitation Cards-2016.12月计科院赛(最短路)
- jvm调优+jstat
- Android Studio项目怎么导入Coding上创建的版本库
- 一个被弃用输出PDF,预览DOC格式的方法
- 文章标题 CSU 1756 :Prime
- MyBatis学习总结(二)——使用MyBatis对表执行CRUD操作