Tips: This article based on Scott Meyers's <<Effective C++>> article 27: Minimize Casting

C++规则的设计目标之一,是保证"类型错误"绝对不可能发生。理论上你的程序可以很“干净”的通过编译,就表示它并不企图在任何对象身上执行任何不安全的,无意义的,愚蠢荒谬的操作。这是一个极具价值的保证,可别草率的放弃。

但是,转型(casting)却破坏了类型系统(type system)。

C++提供了三中不同类型的转化风格:

  • C风格的转换: (T)expression
  • 函数风格的转换: T(expression)
  • 新式风格的转换(new-style or C++ style casts)

C++提供了4种形式的新式转换,每种形式的转换如下:

1)const_cast<T>(expression): const_cast 通常被用来将对象的常量性转除(cast away the constness)。它也是唯一具有此能力的的C++-style转型操作。

2)dynamic_cast<T>(expression): dynamic_cast 主要用来执行“安全向下转型”(Safe downcasting), 也就是用来决定某对象是否归属继承体系中的某个类型。

它是唯一一个无法用旧式语法执行的动作,也是唯一可能消耗重大运行成本的转型动作!(注:旧式风格的转换无法实现父类对象到子类对象的转换)

3) reinterpret_cast<T>(expression): 意图执行低级转型,实际动作(及结果)可能取决于编译器,这就表示它不可移植。这个转换多用在低级代码中。

4)static_cast<T>(expression):用来强迫隐式转换(implicit conversions), 例如将non-const对象转换为const对象,或将int转换位double等等。它也可以来

执行上述多种转换的反向转换。例如,其可以将void型的指针转换位typed型的指针,将pointer-to-base 转换为pointer-to-derived。但它无法将const转换成no-const,这

只有const_cast才能办得到!

许多程序员相信,转型其实什么都没有做,只是告诉编译器将一种类型视为另一种类型,这是错误的观念! 下面我们通过一个实例来验证转型期间编译器确实是做了什么!


 1 #include <iostream>
 2 #include <string>
 3 
 4 using namespace std;
 
 /* 多重继承  */
 class Base1
 {
     public:
         Base1(const string& aName)
         {
             name = aName;
         }
 
         string getName()
         {
             return name;
         }
     private:
         string name;
 };
 
 class Base2
 {
     public:
         Base2(const string& aAddress)
         {
             address = aAddress;
         }
 
         string getAddress()
         {
             return address;
         }
     private:
         string address;
 };
 
 class Drive : public Base1,public Base2
 {
     public:
         Drive(const string& aName,const string& aAddress)
             :Base1(aName),Base2(aAddress)
         {
             
         }
 };
 
 int main(void)
 {
     Drive drive_obj("jiang heng","fudan university");
     Base1* base1_pt = &drive_obj;
     Base2* base2_pt = &drive_obj;
     Drive* drive_pt = &drive_obj;
     cout<<"The Address of base1_pt: "<<base1_pt<<endl;
     cout<<"The Address of base2_pt: "<<base2_pt<<endl;
     cout<<"The Address of drive_pt: "<<drive_pt<<endl;
     return ;
 }

结果:

The Address of base1_pt: 0x7fffc9c64290
The Address of base2_pt: 0x7fffc9c64298
The Address of drive_pt: 0x7fffc9c64290

上述的实例表明C++中单一对象(例如上面的Drive对象)可以有一个以上的地址(比如上面Base2*类型的地址和Drive*类型的地址):

由此得到一个重要的结论对象的布局方式和它们的地址计算方式随着编译器的不同而不同,那意味着“由于知道对象如何布局”而设计的转型,

在某一平台上行得通,而在其他平台上不一定行得通。

关于转型的一个重要事实是你可能因此写出许多是是而非的代码,下面就是这样的一个实例:


1 #include <iostream>

 2 
 3 using namespace std;
 4 
 5 class Window
 6 {
 7     public:
 8         Window(const int& wd,const int& hg)
 9         :width(wd),height(hg)
         {
 
         }
 
         int getWidth()
         {
             return width;
         }
 
         int getHeight()
         {
             return height;
         }
 
         virtual void onResize()
         {
             width += ;
             height += ;
         }
 
         void printWindowMsg()
         {
             cout<<"The height of the window: "<<height<<endl;
             cout<<"The width of the window: "<<width<<endl;
         }
     private:
         int width;
         int height;
 };
 
 class SpecialWindow : public Window
 {
     public:
         SpecialWindow(const int& wd,const int& hg,const int& cor)
         :Window(wd,hg)
         {
             color = cor;
         }
 
         virtual void onResize()
         {
             static_cast<Window>(*this).onResize();
             color += ;    
         }
 
         void printWindowMsg()
         {
             Window::printWindowMsg();
             cout<<"The color of this window is: "<<color<<endl;
         }
     private:
         int color;
 };
 
 int main(void)
 {
     SpecialWindow spwind(,,);
     spwind.onResize();
     spwind.printWindowMsg();
     return ;
 }
     

结果:

The height of the window: 30
The width of the window: 
The color of this window is: 

static_cast<Window>(*this).onResize();

上面这条语句将*this转型成Window,对函数onResize的调用因此调用了Window::onResize。但其调用的并不是当前对象上的函数,而是稍早转型

动作所创建的"*this对象之base class成分"的暂时副本上的OnResize!

将上面的语句改成:

Window::onResize();

结果:

The height of the window: 230
The width of the window: 
The color of this window is: 

显然达到了我们想要的效果!

上面这个例子说明了,如果你在程序中遇到了转型,那么这活脱脱就是一个警告信号!

tips: 出了要对一般的转型保持机敏和猜疑,更应该在注重效率的代码中对dynamic_cast保持机敏和猜疑!

只所以需要dynamic_cast,通常是因为你想在一个你认定为derived class对象身上执行derived class操作函数,但是你的手上只有一个"指向base"的pointer或reference,

你只能靠它们来处理对象!

下面是结局上述问题的一般的做法:

  • 使用容器并在其中存储直接指向derived class对象的指针(通常是智能指针),这样就消除了"通过base class接口处理对象"的需要。
  • 通过base class接口处理"所有可能之各种Window派生类",即在base class内提供virtual函数做你想对各个Window派生类想做的事。

优秀的C++代码很少使用转型,但要说完全摆脱它门又是不切实际的。

C++ 中的类型转换机制详解的更多相关文章

  1. JAVA中的GC机制详解

    优秀Java程序员必须了解的GC工作原理 一个优秀的Java程序员必须了解GC的工作原理.如何优化GC的性能.如何与GC进行有限的交互,因为有一些应用程序对性能要求较高,例如嵌入式系统.实时系统等,只 ...

  2. Tomcat与Spring中的事件机制详解

    最近在看tomcat源码,源码中出现了大量事件消息,可以说整个tomcat的启动流程都可以通过事件派发机制串起来,研究透了tomcat的各种事件消息,基本上对tomcat的启动流程也就有了一个整体的认 ...

  3. C++ 中的RTTI机制详解

    前言 RTTI是”Runtime Type Information”的缩写,意思是运行时类型信息,它提供了运行时确定对象类型的方法.RTTI并不是什么新的东西,很早就有了这个技术,但是,在实际应用中使 ...

  4. 关于MySQL中的锁机制详解

    锁概述 MySQL的锁机制,就是数据库为了保证数据的一致性而设计的面对并发场景的一种规则. 最显著的特点是不同的存储引擎支持不同的锁机制,InnoDB支持行锁和表锁,MyISAM支持表锁. 表锁就是把 ...

  5. JAVA中的反射机制 详解

    主要介绍以下几方面内容 理解 Class 类 理解 Java 的类加载机制 学会使用 ClassLoader 进行类加载 理解反射的机制 掌握 Constructor.Method.Field 类的用 ...

  6. react第五单元(事件系统-原生事件-react中的合成事件-详解事件的冒泡和捕获机制)

    第五单元(事件系统-原生事件-react中的合成事件-详解事件的冒泡和捕获机制) 课程目标 深入理解和掌握事件的冒泡及捕获机制 理解react中的合成事件的本质 在react组件中合理的使用原生事件 ...

  7. 从mixin到new和prototype:Javascript原型机制详解

    从mixin到new和prototype:Javascript原型机制详解   这是一篇markdown格式的文章,更好的阅读体验请访问我的github,移动端请访问我的博客 继承是为了实现方法的复用 ...

  8. 浏览器 HTTP 协议缓存机制详解

    最近在准备优化日志请求时遇到了一些令人疑惑的问题,比如为什么响应头里出现了两个 cache control.为什么明明设置了 no cache 却还是发请求,为什么多次访问时有时请求里带了 etag, ...

  9. JVM的垃圾回收机制详解和调优

    JVM的垃圾回收机制详解和调优 gc即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存.java语言并不要求jvm有gc,也没有规定gc如何工作.不过常用的jvm都有gc,而且大多数gc都 ...

随机推荐

  1. 剑指OFFER之从1到n中出现1的次数(九度OJ1373)

    题目描述: 亲们!!我们的外国友人YZ这几天总是睡不好,初中奥数里有一个题目一直困扰着他,特此他向JOBDU发来求助信,希望亲们能帮帮他.问题是:求出1~13的整数中1出现的次数,并算出100~130 ...

  2. linux添加ssh用户

    正好有朋友问,就转过来分享下. 转自:http://blog.sina.com.cn/s/blog_6fc583e70100n6rm.html 测试环境:CentOS 5.5 1.添加用户,首先用ad ...

  3. 正则表达式30分钟入门教程<转载>

    来园子之前写的一篇正则表达式教程,部分翻译自codeproject的The 30 Minute Regex Tutorial. 由于评论里有过长的URL,所以本页排版比较混乱,推荐你到原处查看,看完了 ...

  4. Cocos2d-x 3.x 资料整理

     cocos2d-x-3.0rc0新project的分辨率设置和控制台输出信息 http://kome2000.blog.51cto.com/969562/1379704 Cocos2d-x 3. ...

  5. 文件I/O之fcntl函数

    fcntl函数可以改变已打开的文件的性质. #include <fcntl.h> int fcntl( int filedes, int cmd, ... /* int arg */ ); ...

  6. 0x800a1391-Microsoft Jscript "JSON未定义"

    本人在进行调试代码是遇到以下问题: 在运行到var result = JSON.parse(data);这句时,报错:JSON未定义.如下图:

  7. 一款仿36氪iOS版APP源码

    Features 离线缓存 解决视频播放器的网速慢卡顿 视频播放器调用简单 cell自适应高度 cell中嵌套webView cell中嵌套webView 条件实时搜索 Known problems ...

  8. smarty实现缓存

    首先需要在mySmarty中添加配置信息,开启缓存,设置缓存文件存放目录,设置缓存时间缓存可以实现减少访问数据库,减轻数据库压力,访问一次数据库,形成静态页面,下次直接调用这个页面,也可以用nocac ...

  9. Android——列表视图(ListView)

    列表视图是android中最常用的一种视图组件,它以垂直列表的形式列出需要显示的列表项.在android中有两种方法向屏幕中添加列表视图:一种是直接使用ListView组件创建:另外一种是让Activ ...

  10. mysqlimport 导入文件到数据库命令

    mysqlimport -h 172.16.145.125 -u ocetl -pocetl test  --fields-terminated-by='|' '/home/ocetl/tmp_use ...