条款27:尽量少做转型动作

条款27:尽量少做转型动作
    (Minimize casting.)
   
内容:
    我们先来看一下转型语法,C风格的转型语法大概就是下面这种形式:
    (T)expression     //将expression转换为T类型
    函数转型语法如下:
    T(expression)     将expression转换为T类型
    这两种转型语法,我们称之为"旧式转型",既然是"旧式",那当然有"新式转型"啰,当然有,C++中提供了四种"新式转型",我大概将它们的适应的范围介绍一下(关于新式转换的详细的介绍,我在C++语言基础分类中的已经写了一篇,请注意浏览,呵):
    ■ static_cast 用来进行强制隐式转换,我们平时遇到的大部分的转型功能都通过它来实现.例如将int转换为double
,将void*转换为typed指针,将non-const对象转换为const对象,反之则只有const_cast能够完成.
    ■ const_cast 用来将对象的const属性去掉,功能单一,使用方便,呵呵.
    ■ dynamic_cast 用于继承体系下的"向下安全转换",通常用于将基类对象指针转换为其子类对象指针,它也是唯一一种无
法用旧式转换进行替换的转型,也是唯一可能耗费重大运行成本的转型动作.
    ■ reinterpret_cast 低级转型,结果依赖与编译器,这因为着它不可移植,我们平常很少遇到它,通常用于函数指针的
转型操作.
    由于新式转型加入C++标准较晚,现在很多人都已经习惯用旧式转型,但随着你对新式转型的了解程度的深入,你就会发
现新式转型与早有的旧式转型相比,有两大优点:(1)它很容易在代码中被识别出来;(2)由于各个转型动作的目标都有自己的
适用范围,这就使得当你在用错的情况下,编译器能够诊断出你的错误.由于这两优点的存在越来越多的人已经从使用"旧式转
型"阵营转投到"新式转型"阵营,这使得"新式转型"越来越受到程序员的青睐.
    当我们决定开始使用"新式转型"时,我们平时的一些看似很合理的习惯需要改正啰,最难改正的就数函数式转型了,看下面这
个例子:
    class Widget{
    public:
        explicit Widget(int size);
        ...
    };
    void doSomething(Widget& w);
    doSomething(Widget(15)); //"旧式转型"中的函数转型
    doSomething(static_cast<Widget>(15));//"新式转型"
    看到上面的"新式转型"的写法好像不像"生成对象",所以你很可能使用函数式转型而不使用static_cast.这里我要说的是当我们写下一段以后会导致出错的核心代码时,而在写下它们的稍后你往往会"觉得"通情达理,所以或许最好忽略你的感觉,始终理智地使用新式转型.
    请不要认为转型其实就是把某种类型视为另一种类型,任何一种转型动作往往真的令编译器额外地编译出运行期间执行的代
码,例如将int转型为double就会发生这种情况,因为在大部分的计算器体系结构中,int的底层表述不同于double的底层表述.这里我们还要提到一个经常被我们忽略的问题:单一的对象可能拥有一个以上的地址(例如:"以base*指向它"时的地址和"以Derived*指向它"时的地址),实际上一旦使用多重继承,这事几乎一直发生.即使在单一继承中也可能发生.恐怖!为什么会发生这样的事情呢?因为对象的布局方式和它们的地址计算发式随着编译器的不同而不同,这就以为着写出"根据对象如何布局"而写出的转型代码在某一平台上行得通,在其它平台上则不一定.往往这种错误的出现让很多有程序员甚至是一些很有经验的程序员眉头紧锁.
    关于转型的另外一件事情要注意的事情就是:我们很容易写出一些貌似很合理的代码,而往往运行的结果却不是我们想要的,比如我们来看下面这个段代码:
    class Window{
    public:
        virtual void onResize(){...}
        ...
    };
    class SpecialWindow:public Window{
    public:
        virtual void onResize(){
            static_cast<Window>(*this).onResize();//调用基类的实现代码
            ... //这里进行SpecialWindow的专属行为.
        }
        ...
    };
    这里会发生什么问题呢?static_cast<Window>(*this)这个转型动作并不是如你想象的那样得到当前对象的基类对象部分,其实编译器为你产生了的是----->基类对象的副本,喔欧,那就糟了,我执行的onResize方法压根就没有执行到基类对象上,
SpecialWindow的专属onResize却执行在子类对象上,我使得这个对象处于一种"伤残"状态.shit!怎么办?你只有老老实实的这样改写代码:
    void SpecialWindow::onResize(){
        Window::onResize(); //此时才是真正的调用基类部分的onResize实现.
        ...     //同上
    }
    知道随意做转型动作带来的危险了吧?呵呵.
    我们再来说另外一个问题,上面我们提到了dynamic_cast可能会耗费大量的转型成本,这句话怎么理解?因为dynamic_cast的许多实现版本执行速度相当慢.例如至少有一个很普遍的实现版本是基于"class名称之字符串比较",如果你在四层深的单继承体系内的某个对象身上执行dynamic_cast就可能耗用四次strcmp调用来比较class名称.深度继承或多重继承的成本更高,这里我就强调除了对一般转型保持机敏与猜疑,更应该在注重效率的代码中对dynamic_casts保持机敏与猜疑.如果你想要大量用dynamic_cast完成转型操作,请寻找其它策略进行避免它,这无疑会提升你的代码效率到一个可观的水平.
    今天说的太多了,打字也挺累的,希望大家能够理解我要表达的内容.
    请记住:
    ★ 如果可以,尽量避免转型,特别是在注重下来的代码中避免dynamic_cast.如果有个设计需要转型动作,试着发展无需转型的替代设计.
    ★ 如果转型是必要的,试着将它隐藏于某个函数背后.客户随后可以调用该函数,而不需将转型放进它们自己的代码内.
    ★ 宁可使用C++-style(新式)转型,不要使用旧式转型.前者很容易辨识出来,而且也比较有着分门别类的职掌.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值