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. 利用接口做参数,写个计算器,能完成+-*/运算 (1)定义一个接口Compute含有一个方法int computer(int n,int m); (2)设计四个类分别实现此接口,完成+-*/运算 (3)设计一个类UseCompute,含有方法: public void useCom(Compute com, int one, int two) 此方法要求能够:1.用传递过来的对象调用compute

    package com.homework5; public interface Compute { //声明抽象方法 int computer(int n,int m); } package com. ...

  2. Lync边缘服务器配置

    以下步骤均使用Lync管理员权限即可完成 1.在前端下载并编辑拓扑,新建边缘池 如果边缘池中只有一台服务器,则池名称与服务器名称相同,如下: 如果需要删除边缘池,则需要先取消关联,如下: 2.发布拓扑 ...

  3. git 回退和删除操作

    今天不小心把分支的commit提交到master上了.衰 主要通过下面几个命令解决了,很简单记录一下. git reset –hard  回退到某一个版本git push origin :xxxx  ...

  4. POJ 3080 Blue Jeans (KMP)

    求出公共子序列  要求最长  字典序最小 枚举第一串的所有子串   然后对每一个串做KMP.找到目标子串 学会了   strncpy函数的使用   我已可入灵魂 #include <iostre ...

  5. 【问题汇总】ListView的FooterView设置可见性的问题

    ListView的FooterView一般用来给用户展示一些提示信息. 正常情况下,是这么使用的.代码例如以下: // footer footerLayout = new PullLoadingLay ...

  6. 文件I/O(不带缓冲)之dup和dup2函数

    下面两个函数都可用来复制一个现有的文件描述符: #include <unistd.h> int dup( int filedes ); int dup2( int filedes, int ...

  7. PAT 1011

    1011. World Cup Betting (20) With the 2010 FIFA World Cup running, football fans the world over were ...

  8. 一、 Socket之UDP异步传输文件

    用SCOKET 发送文件是一个不太好处理的问题,网上的例子也都是很简单的,我准备写一个比较完善的例子,这个就算是开始吧,以后的都会在这个例子的基础上进行修改,准备实现多线程传输.断点传输和文件传输的完 ...

  9. global 用法

    <?php//$GLOBALS['he']="hechunhuae";function Test(){ //global $he; $GLOBALS['he']=" ...

  10. linux下修改环境变量

    把/etc/apache/bin目录添加到PATH中,方法有三: 1.#PATH=$PATH:/etc/apache/bin 使用这种方法,只对当前会话有效,也就是说每当登出或注销系统以后,PATH ...