27 尽量少做转型动作

一、旧式转型(C风格)

  • (T)expression 强制转型,将expression转型为T
  • T(expression) 函数式转型,将expression转型为T

二、新式四种转型(C++风格)

  • const_cast(expression)
    通常被用来将对象的常量性转除。唯一有此能力的C+±style转型操作符。
  • dynamic_cast(expression)
    主要用来执行“安全向下转型”,也就是用来决定某对象是否归属继承体系中的某个类型。它是唯一无法用旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作。
  • reinterpret_cast(expression)
    意图执行低级转型,实际动作(及结果)可能取决于编译器,这也表示它不可移植。如将一个pointer to int 转型为一个int
  • static_cast(expression)
    用来强迫隐式转换,例如将non-const对象转换为const对象,或将int转为double等等。也可以用来执行上述多种转换反向转换,如将void* 指针转换为typed指针,将pointer-to-base转为pointer-to-derived。但无法将const转为non-const。

三、C++转型的优点

  • 很容易在代码中被辨识出来(使用工具如grep),因而得以简化“找出类型系统在哪个地点被破坏”的过程。
  • 各转型动作的目标愈窄化,编译器愈可能诊断出错误的运用。如:打算去掉const,除非使用const_cast,否则无法通过编译。

四、编译器为转型做了什么?

任何一个类型转换(不论是通过转型操作而进行的显示转换,或通过编译器完成的隐式转换)往往真的令编译器编译出运行期间执行的代码。

//情形一
int x,y
...
double d = static_cast<double>(x) / y;

//情形二
class Base {...};
class Derived: public Base {...};
Derived d;
Base* pb = &d;
  • 将int x转型为double肯定会产生一些代码,因为大部分计算器体系结构中,int的底层表述不同于double的底层表述。
  • derived class转换为base class,有时候两个指针的值并不相同,这种情况下会有个偏移量在运行时期被施于Defived指针身上,用以取得正确的Base指针值,尤其是在多继承时。

五、derived class内的virtual函数代码的第一个动作先调用base class的对应函数

class Window{
public: 
    virtual void onResize(){ ... } //base onResize实现代码
    ...
};
class SpecialWindow:public Window{
public:
    virtual void onResize(){                  //derived onResize实现代码
        static_cast<Window>(*this).onResize();//将*this转型为Window,然后再调用其onResize;这样可不行
        ...
    }
};

转型操作会对当前对象建立副本。上述代码将当前对象(*this)转型为Window,再调用onResize。实际上是在当前对象转型所建立的副本对象上调用的onResize。之后在执行当前对象上的onResize内的代码。函数虽然只有一份,但是对象不同,函数如果要修改数据,那么修改的就是不同对象上的数据
正确的做法

class SpecialWindow:public Window{
public:
    virtual void onResize(){                 
        Window::onResize();//调用其Window::onResize作用于*this身上
        ...
    }
};

六、dynamic_cast

  • dynamic_cast的许多实现版本执行速度相当慢,如:一个很普遍的实现版本基于“class 名称字符串比较”,在四层次深的单继承体系内的某个对象上执行dynamic_cast,将会耗用多达四次的strcpm调用,用以比较class名称。
  • 使用dynamic_cast的原因:想要执行derived class中的函数,但是手头只有“指向base”的pointer或reference。
    不要这样写:
class Window {...};
class SpecialWindow :public Window {
public:
    void blink();
    ...
};
typedef std::vector<std::tr1::shared_ptr<Window> > VPW;
VPW winPtrs;
...
for(VPW::iterator iter = winPtrs.begin();iter!=winPtrs.end();++iter){
    if(SpecialWindow* psw = dynamic_cast<SpeciaoWindow*>(iter->get())){
        psw->blink();
    }
}

应该这样做:

typedef std::vector<std::tr1::shared_ptr<SpecialWindow> > VPSW;
VPSW winPtrs;
...
for(VPW::iterator iter = winPtrs.begin();iter!=winPtrs.end();++iter){
     (*iter)->blink();
}

这样做无法在一个容器内存储指针“指向所有可能之各种Window派生类”。如果需要达成此目标,可以在base class内提供virtual函数做你想对各个Window派生类做的事。

  • 避免“连串dynamic_casts”
class Window {...};
...
typedef std::vector<std::tr1::shared_ptr<Window> > VPW;
VPW winPtrs;
...
for(VPW::iterator iter = winPtrs.begin();iter!=winPtrs.end();++iter){
      if(SpecialWindow1* psw = dynamic_cast<SpeciaoWindow1*>(iter->get())){...}
      else if(SpecialWindow2* psw = dynamic_cast<SpeciaoWindow2*>(iter->get())){...}
      else if(SpecialWindow3* psw = dynamic_cast<SpeciaoWindow3*>(iter->get())){...}
      ...
}

这样产生出的代码又大又慢,而且基础不稳。因为每次Windows class继承体系一有改变,所有这一类代码都必须再次检阅看看是否需要修改。优良的C++代码很少使用转型

七、总结

  • 如果可以,尽量避免转型,特别实在注重效率的代码中避免dynamic_casts。如果有个设计需要转型动作,试着发展无需转型的替代方案。
  • 如果转型是必须的,试着将他隐藏于某个函数背后。客户随后可一个调用该函数,而不需要将转型放进他们自己的代码内。
  • 宁可使用C+±style(新式)转型,不要使用旧式转型。前者很容易被辨识出来。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值