接触过C语言的同学可能很喜欢使用C风格的强制转换(也就是一般形式的强转)。
(类型)变量
用起来很方便,可以显式的把某些类型转换成我们想要的类型,比如(int*)字符。这样的使用对常见的内置类型可谓是屡试不爽,但是对于C++中的对象用起来却是危机四伏。所以dynamic_cast应运而生。
我们很容易查到dynamic_cast 的定义是将一个基类对象指针(或引用)cast到继承类指针,dynamic_cast会根据基类指针是否真正指向继承类指针来做相应处理。简单来说,就是能判断一个对象指针(引用)指向的是我<>传入的这个类型,而不是他的基类类型。
所以dynamic_cast与一般强转的第一个区别就是,dynamic_cast应用的范围要更小一些,他只能对指针或者引用来进行强转。
进一步来讲,dynamic_cast的诞生其实是归结于RTTI本身。因为通常在一个庞大的项目,我们更多地用的是别人的库,用的是并不知道哪里传过来的指针,所以我们有的时候就是想在运行的时候来区分某一个指针是基类类型还是他的子类类型。而dynamic_cast能通过执行两步操作完成我们的目标,第一,判断是不是某个类型,第二,如果是的话就把该指针强转成你要的这个类型。
所以相对于一般的强转,第二个区别就是,dynamic_cast多了一步安全的检查。
如果没有检查,可能出现哪些问题?
我们知道基类对象是不允许转换为子类对象的,因为派生类可能拥有基类没有的成员,内存的占用可能也不同。但是基类的指针却可以强转为派生类的指针,在这个转换当中,我们就可能把一个指向基类的指针指向一个派生类,那么同时就可以调用这个派生类的成员。当我们调用派生类成员的时候,就相当于用派生类的内存布局来解释基类的内存。
下面,简单举个例子,大概的内存布局如下所示(成员变量写为public便于后面描述),
Base*pb1 = new Base();
Base b;
Child *pc1 = new Child();
Child c;
那后续的使用上会出现什么情况呢?
1. 当我们读取某一个成员变量时,基类没有该成员,这个成员变量是非指针内置类型
比如调用cout<<((Child*)pb1)->ChildNum;,那么很明显,因为我们根本没有初始化该成员,得到一个不可预测的结果,确切的是地址1012这个地方放的不知道是什么东数据,而我们非要把他解读为ChildNum。
2. 当我们读取某一个成员变量时,基类没有该成员,这个成员变量是指针内置类型
比如调用cout<<((Child*)pb1)->pChild;,同理,也是得到一个未知的不可预测的数据。不过这样还好,也不会崩溃。但是如果执行cout<<*((Child*)pb1)->pChild;,那百分之99%就要崩溃了,因为这个指针你完全不清楚他指向哪里,可能是一个未被使用的地址。
3. 当我们读取某一个成员变量时,基类没有该成员,这个成员变量是非指针(指针)对象类型
这与上面的情况是相似的,对象里的任何数据都是未初始化的。只不过对象里面可能又更多更为复杂的数据成员。
4. 当我们调用某一个成员函数时,还是要看他是否动用了对应的成员变量,如果使用了,那么就像上述几种情况一样,很有可能出现问题。
其实说白了,一般强转的问题就是你得到的成员数据都是未初始化的。而未初始化的指针最为危险,崩溃问题基本上就是因为指针没有妥善的处理(调用空指针,调用已经释放的指针等)。
前面描述了对基类指针使用一般强转造成的危险,那么如果是将派生类对象转换为基类对象呢?结果就是我们常说的切片(slience)。
切片简单来讲,就是把派生类有的基类没有的部分给切掉。这样,你调用被切过的派生类的成员肯定会出问题的。切片问题涉及到C++内部的编译器优化以及拷贝构造函数等内容,大家可以参考http://blog.chinaunix.net/uid-7471615-id-83778.html以及书籍深入探索C++对象模型 http://vdisk.weibo.com/s/GaLUmHebxGBF。博主有时间会写一篇分析切片的博客。
关于dynamic_cast还可以参考C++语言的设计与演化 14章 强制转换