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. xcode中create groups 和 create folder reference 的区别

    (文章为博主原创,未经允许,不得转载!) 今天在项目中搭建框架忽然发现工程中有黄蓝文件夹的区别,而且对应到不同的情况: 1.蓝色文件夹下文件不能被读取: 2.蓝色文件夹下创建新的文件类会直接跳过选择类 ...

  2. Java向PostgreSQL发送prepared statement 与 libpq 向PostgreSQL发送prepared statement之比较:

    Java 代码,在数据库端,并没有当成 prepared statetment 被处理. C代码通过libpq 访问数据库端,被当成了 prepared statement 处理.也许是因Postgr ...

  3. 关于【cocos2dx-3.0beta-制作flappybird】教程在3.2project中出现找不到CCMenuItem.h的解决方法

    文章原文:http://blog.csdn.net/kantian_/article/details/36187141 作者升级源码.能够在3.1平台下执行. 我的是vs2013+cocos2dx-3 ...

  4. C++11 std::bind std::function 高级使用方法

    从最基础的了解,std::bind和std::function /* * File: main.cpp * Author: Vicky.H * Email: eclipser@163.com */ # ...

  5. 在iOS中怎样创建可展开的Table View?(上)

    原文地址 本文作者:gabriel theodoropoulos 原文:How To Create an Expandable Table View in iOS 原文链接 几乎所有的app都有一个共 ...

  6. Android下实现GPS定位服务

    1.申请Google API Key,参考前面文章 2.实现GPS的功能需要使用模拟器进行经纬度的模拟设置,请参考前一篇文章进行设置 3.创建一个Build Target为Google APIs的项目 ...

  7. iOS开发——面试笔试精华(一)

    面试笔试精华(一) 1.        #import 跟#include.@class有什么区别?#import<> 跟 #import”"又什么区别? 1>  #imp ...

  8. android的界面编程

    主要是用View以及ViewGroup,同时ViewGroup又是View的子类,充当容器. 主要有两种方法控制view的行为: 1.在XML布局文件中通过XML属性进行控制 2.在Java代码中通过 ...

  9. [MethodImpl(MethodImplOptions.Synchronized)]

    在NopCommerce项目的Nop.Core类库中有一个EngineContext类中有一个Initialize方法用到了[MethodImpl(MethodImplOptions.Synchron ...

  10. 3D分析之3D要素工具箱(转)

    来自:http://blog.csdn.net/kikitamoon/article/details/8193764 整理有关 ArcGIS 10.1 3D分析工具箱中,3D Feature 工具箱中 ...