新式转型操作符[条款9] --《C++必知必会》
在旧式转型(cast)下面隐藏着一些见不得人的、鬼鬼祟祟的东西。他们的语法形式使其在一段代码中通常很难引起人们的注意,但它们可能会搞一些可怕的破坏活动,就好比你冷不丁被一个恶棍猛击一拳似的。让我们阐明旧式转换的含义。显然,在最初的C语法中,在表达式中将类型加括号就是旧式转型:
char * hopeItWorks = (char *)0x00ff000;//旧式转型
C++引入了另一种转型,即采用函数形式的转型语法来表达同样的意思:
typedef char * pChar;
hopeItWorks = pChar( 0x00ff0000 ); //函数形式/旧式转型
函数形式的转型也许看上去比它那可怕的老祖先斯文一些,但实际上同样龌龊,应该像躲避瘟疫一样躲避他们。
诚实可靠的程序员使用新式转型操作符,因为它们能够更精确地表达意思。有四个新式转型操作符,每一个都有这特定的用途。
const_cast操作符允许添加和移除表达式中类型的const 或 volatile 修饰符:
const Person * getEmployee(){...};
Person * anEmployee = const_cast< Person * >( getEmployee());
在以上代码中,使用const_cast来剥除getEmployee返回类型中的const修饰符。可以使用旧式转型获得相同的效果:
anEmployee = (Person *)getEmployee();
但使用const_cast的做法更好,这有几个方面的原因。首先,它看上去丑陋、醒目,就像一个受伤的拇指一样从代码中伸出来。这是件好事,因为任何形式的转型都存在危险。所以,它们写起来应该很痛苦,只有当不得已时才键入它们。它们还应该易于发现,因为无论何时当代码中出现bug时,人们应该首先检查转型这个嫌犯。其次,const_cast要比旧式转型的威力小,因为它只影响类型修饰符。这个限制同样是件好事,因为它允许我们更精确地表达意图。使用旧式转型等于告诉编译器,“你给我闭嘴,因为我希望将getEmployee的返回类型转换为Person * ”。而使用const_cast则等于告诉编译器,“我只希望将getEmployee的返回类型的const去掉”。就目前来看,这两种语句没有多大的差别(尽管实际上他们都很无礼),但是对getEmployee函数进行了一些维护性的修改后,情况就不同了:
const Employee * getEmployee();//大修改
旧式转型所强加的规则现在任然有效,编译器将不会对从const Employee* 到Person * 这个不正确的转换做出反应,但如果使用const_cast,编译器将会发出抱怨,因为这么剧烈的转换已经超出了它的能力范围。简而言之,const_cast由于旧式转型,因为它更丑陋、更难用,并且威力更小。
static_cast操作符用于相对而言可跨平台移植的转型。最常见的情况是,它用于将一个继承层次结构的基类的指针或引用,向下转型为一个派生类的指针或引用:
Shape * sp = new Circle;
Circle * cp = static_cast<Circle *>( sp );向下转型
在这个例子中,使用static_cast将会产生正确的代码,因为sp确实指向一个Circle对象。然而,如果sp指向其他类型的Shape( Shape的其它派生类),那么当时使用cp时,很可能会得到某种运行期间错误。因此,重申一遍,虽然这种新式转型操作符比旧式转型安全一些,但还不够安全。
注意,static_cast无法像const_cast那样改变类型的修饰符。这意味着,有时需要使用由两个新式转型操作符构成的转型序列,来获得单个旧式转型所能表达的效果:
const Shape * getNextShape(){...}
//...
Circle * cp = static_cast< Circle * > ( const_cast< Shape * >( getNextShape()));
标准没有对reinterpre_cast的行为提供太多的保证,不过它通常的行为可以顾名思义。它从位( bit )的角度看待一个对象,从而允许将一个东西看作另一个完全不相同的东西:
hopeItWorks = reinterpre_cast< char * >( 0x00ff0000); //把int假装成指针
int * hopeless = reinterpre_cast< int * >( hopeItWorks );//把char* 假装成 int *
这类东西在底层编码里偶尔非用不可,但它可能不具移植性。你要谨慎行事。注意区分当分别使用reinterpre_cast 和static_cast将指向基类的指针向下转型为指向派生类的指针时的行为。reinterpre_cast通常只是将基类指针假装成一个派生类指针而不改变其值,而static_cast(以及旧式转型---从这个角度来说) 则将执行正确的地址操作。
在类层次结构的范畴谈转型,就会涉及到dynamic_cast。dynamic_cast通常用于执行从指向基类的指针安全地向下转型为指向派生类的指针。不同于static_cast的是,dynamic_cast仅用于对多态类型进行向下转型(也就是说,被转型的表达式的类型,必须是一个指向带有虚函数的类类型的指针),并且执行运行期间检查工作,来判定转型的正确性。当然,这种安全性的获得是要付出代价的。使用static_cast通常无需付出(或极少付出)运行时代价,而使用dynamic_cast则意味着要付出显著的运行时开销。
const Circle *cp = dynamic_cast< const Circle *>( getNextShape());
if ( cp ) {...}
如果getNextShape返回一个指向Circle的指针(或者从Circle共有派生的东西,换句话说,一些和Circle之间存在着 is-a 关系的东西。), 那么转型是成功的,并且 cp 将会指向一个Circle。注意,我们可以将生命和测试结合与同一个表达式中:
if( const Circle *cp = dynamic_cast<const Circle *>( getNextShape())){...}
这样做是有好处的,因为它将变量cp的作用于限制在if语句之内,因此,当不再使用它时,cp将会离开作用域(并被销毁)。
有关dynamic_cast_一个不太常见的用法是对引用类型执行向下转型:
const Circle & rc = dynamic_cast<const Circle &>( *getNextshape() );
这个操作类似于对指针类型的dynamic_cast操作,不过如果转型失败,操作法将抛出一个std::bad_cast异常而不是仅仅返回一个空指针(记住,不存在空引用)。习惯上,对一个指针进行dynamic_cast等于在说:“这个Shape指针真的指向一个Circle吗?如果不是,我可以处理这种情况”。而对一个引用执行dynamic_cast则等于声明一个不变式(invariant):“这个Shape应该是一个Circle,否则,肯定是哪儿出了严重的错误”。
与其它新式转型操作符相比,dynamic_cast只是偶尔需要使用,但因为背上了“安全”的名声,所以常常被滥用。
第一个代码例子:(Visual C++ 6.0)
#include<iostream> class Shape{ public: virtual ~Shape(){}; virtual void draw()=0; }; class Rollable{ public: virtual ~Rollable(){}; virtual void roll()=0; }; class Circle:public Shape,public Rollable{ public: void draw(){std::cout<<"\n:画圆\n";} void roll(){std::cout<<"\n:滚圆\n";} }; class Square:public Shape{ public: void draw(){std::cout<<"\n:画矩形\n";} }; class Ball:public Shape,public Rollable{ public: void draw(){std::cout<<"\n:画球\n";} void roll(){std::cout<<"\n:滚球\n";} }; int main(int argc, char* argv[]) { Shape * spCircle=new Circle; Shape * spSquare=new Square; Shape * spBall = new Ball; //Circle * rollcircle=spCircle; //error C2440:'initializing':cannot convert from 'class Shape *' to 'class Circle *' Circle * rollcircle = static_cast<Circle*>(spCircle); rollcircle->roll(); //输出:滚圆 //Circle * rollcircle=new Circle; Ball * rollball=new Ball; if(Rollable * roller=dynamic_cast<Rollable *>(rollcircle)) //可以 roller->roll();//输出:滚圆 if(Rollable * roller=dynamic_cast<Rollable *>(rollball)) //可以 roller->roll();//输出:滚球 if(Shape * shape=dynamic_cast<Shape*>(rollcircle)) //可以 shape->draw();//输出:画圆 return 0; }
第二个代码例子(C++Builder6.0):
class A{ public: virtual A* func(){}; }; class B:public A{ public: B* func(){} //重写 A A::func() ,重写后的返回类型为B ,因为 B继承于A (B is a A) ,这叫:协变返回类型 }; class C{ public: virtual void func(){} }; class D{}; A *a=new A; B *b=new B; C *c=new C; D *d=new D; A *a_1tmp=dynamic_cast<A*>(b); //编译通过 A *a_2tmp=dynamic_cast<A*>(c); //编译通过 //A *a_3tmp=dynamic_cast<A*>(d); //E2307Type'D'is not a defined class with virtual functions A *a_4tmp=static_cast<A*>(b);//编译通过 //A *a_5tmp=static_cast<A*>(c); //E2031 Cannot cast from 'C*' to 'A*' //A *a_6tmp=static_cast<A*>(d); //E2031 Cannot cast from 'D*' to 'A*'