一、旧式转型(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(新式)转型,不要使用旧式转型。前者很容易被辨识出来。