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. 磐石加密狗NT88管理层API

    磐石加密狗NT88管理层API   直接贴代码了 1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 ...

  2. PowerShell远程安装应用程序

    安装MSI包 使用PowerShell调用WMI对象,你可以执行下面的脚本来安装你的MSI安装包: $box="deviis01" #this is the name of you ...

  3. Codeforces Round #324 (Div. 2) E. Anton and Ira 贪心

    E. Anton and Ira Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/contest/584/probl ...

  4. MST最小生成树及克鲁斯卡尔(Kruskal)算法

    最小生成树MST,英文名如何拼写已忘,应该是min spaning tree吧.假设一个无向连通图有n个节点,那么它的生成树就是包括这n个节点的无环连通图,无环即形成树.最小生成树是对边上权重的考虑, ...

  5. 访谈将源代码的函数 strcpy/memcpy/atoi/kmp/quicksort

    一.社论 继上一次发表了一片关于參加秋招的学弟学妹们怎样准备找工作的博客之后,反响非常大.顾在此整理一下,以便大家复习.好多源自july的这篇博客,也有非常多是我自己整理的.希望大家可以一遍一遍的写. ...

  6. javascript 递归之阶乘

    阶乘,即5! = 5*4*3*2*1, 先看传统的做法,利用while循环实现: function factorial(num){ var result = num; if(num<0){ re ...

  7. [设计模式1]--单例模式(SINGLETON)

    搞笑解释: 俺有6个漂亮的老婆,她们的老公都是我,我就是我们家里的老公Sigleton,她们只要说道“老公”,都是指的同一个人,那就是我 定义: 单例模式确保某一个类只有一个实例,而且自行实例化并向整 ...

  8. mysql服务器辅助选项

    查看控制台命令行前缀 : echo $PS1 ,例如输出 '[\u@\h \w]#     其中,\u是用户名,\h是主机名称: hostname -s 可以查看当前主机名,  hostname 'z ...

  9. [置顶] 博客已迁移至ryantang.me

    大家好,感谢大家一直以来的支持,本博客内容已停止更新,新内容将发布到我的新博客,地址是:ryantang.me,欢迎大家继续支持,我会在ryantang.me上发布内容更丰富的文章内容,谢谢! Rya ...

  10. sscanf、strsep

    #include <stdio.h> #include <string.h> int main() { char token[] ="abdzxbcdefgh&quo ...