GotW #17 Casts
著者:Herb Sutter
翻译:K ][ N G of @rk™
[声明]:本文内容取自www.gotw.ca网站上的Guru of the Week栏目,其著作权归原著者本人所有。译者kingofark在未经原著者本人同意的情况下翻译本文。本翻译内容仅供自学和参考用,请所有阅读过本文的人不要擅自转载、传播本翻译内容;下载本翻译内容的人请在阅读浏览后,立即删除其备份。译者kingofark对违反上述两条原则的人不负任何责任。特此声明。
Revision 1.0
Guru of the Week 条款17:转型
难度:6 / 10
(你对C++转型了解多少?适当的使用它可以极大的提高代码的可靠性。)
[问题]
标准C++中新风格的转型与旧风格的C转型相比,具有更强大的功能和安全性。你对它了解多少?本条款中使用下列类和全局变量:
class A { /*...*/ }; class B : virtual A { /*...*/ }; struct C : A { /*...*/ }; struct D : B, C { /*...*/ }; A a1; B b1; C c1; D d1; const A a2; const A& ra1 = a1; const A& ra2 = a2; char c;1. 下列哪一种新风格的转型不能与C中的转型相对应?
const_cast
dynamic_cast
reinterpret_cast
static_cast
2. 对于下列每一个C中的转型语句,写出相应的新风格转型语句。其中哪一个语句如果不以新风格编写的话就是不正确的?
void f() { A* pa; B* pb; C* pc; pa = (A*)&ra1; pa = (A*)&a2; pb = (B*)&c1; pc = (C*)&d1; }3. 评判下列每一条C++转型语句的编写风格和正确性。
void g() { unsigned char* puc = static_cast<unsigned char*>(&c); signed char* psc = static_cast<signed char*>(&c); void* pv = static_cast<void*>(&b1); B* pb1 = static_cast<B*>(pv); B* pb2 = static_cast<B*>(&b1); A* pa1 = const_cast<A*>(&ra1); A* pa2 = const_cast<A*>(&ra2); B* pb3 = dynamic_cast<B*>(&c1); A* pa3 = dynamic_cast<A*>(&b1); B* pb4 = static_cast<B*>(&d1); D* pd = static_cast<D*>(pb4); pa1 = dynamic_cast<A*>(pb2); pa1 = dynamic_cast<A*>(pb4); C* pc1 = dynamic_cast<C*>(pb4); C& rc1 = dynamic_cast<C&>(*pb2); }
[解答]
1. 下列哪一种新风格的转型不能与C中的转型相对应?
只有dynamic_cast不能与C的转型相对应。其它的新风格转型都能与C中的旧风格转型相对应。
2. 对于下列每一个C中的转型语句,写出相应的新风格转型语句。其中哪一个语句如果不以新风格编写的话就是不正确的?
void f() { A* pa; B* pb; C* pc; pa = (A*)&ra1;应该使用const_cast:const_cast<A*>(&ra1);
pa = (A*)&a2;这一句无法以新风格的转型表达。最接近的方案是使用const_cast,但a2是一个const object,语句执行的结果是未定义的。
pb = (B*)&c1;应该使用reinterpret_cast:pb=reinterpret_cast<B*>(&c1);
pc = (C*)&d1; }这一转型在C中是错误的。而在C++中,并不需要转型:pc=&d1;
3. 评判下列每一条C++转型语句的编写风格和正确性。
首先要注意:我们并不知道本条款中给出的类是否拥有虚拟函数;如果涉及转型的那些类并不拥有虚拟函数,那么下述所有对dynamic_cast的使用都是错误的。在下面的讨论中,我们假设所有的类都拥有虚拟函数,从而使所有的dynamic_cast用法都合法。
void g() { unsigned char* puc = static_cast<unsigned char*>(&c); signed char* psc = static_cast<signed char*>(&c);错误:对两条语句我们都必须使用reinterpret_cast。这一开始或许会使你感到吃惊;这样做的原因是,char、signed char以及unsigned char是三个互不相同、区别开来的型别。尽管它们之间存在着隐式转换,它们也是互无联系的,因而指向它们的指针也是互无联系的。
void* pv = static_cast<void*>(&b1); B* pb1 = static_cast<B*>(pv);这两句都不错,但第一句中的转型是不必要的,因为本来就有从一个对象指针到void*的隐式转型动作存在。
B* pb2 = static_cast<B*>(&b1);这一句不错,但其转型也是不必要的,因为其引数(argument)已经是一个B*。
A* pa1 = const_cast<A*>(&ra1);这一句是合法的,但是使用转型来去掉const-ness(常量性)是潜在的不良风格的体现。在大部分情况下,即当你因合理的缘由而想要去掉指针或引用的const-ness(常量性)时,这都涉及到某些类成员,并通常会使用mutable关键字来完成。请参看GotW#6了解更多关于const-correctness的讨论。
A* pa2 = const_cast<A*>(&ra2);错误:如果该指针被用来对对象施行写操作,那么就会产生未定义行为;因为a2是一个const object。要明白其原因,可以试想如果一个编译器了解到“a2是作为const object而被创建的”这个情况,并出于优化的考虑而将其存放在只读存储区,会发生什么事情。很明显,想通过转型而去掉这样一个对象的const属性是危险的。
注意:我并没有举例显示如何使用const_cast把一个non-const指针转型为一个const指针。因为这样做是多此一举;将一个non-const指针赋值给一个const指针,这本来就是合法的。我们只需要使用const_cast做相反的操作。
B* pb3 = dynamic_cast<B*>(&c1);错误(当你企图使用pb3时发生):这一句会将pb3设置为null,因为c1不是一个(IS-NOT-A)B(因为C不是以public方式派生自B的,且实际上压根儿就不是派生自B的)。这里唯一合法可用的转型就是reinterpret_cast,但使用它也几乎总是很龌龊的。
A* pa3 = dynamic_cast<A*>(&b1);错误:这一句是非法的,因为b1不是一个(IS-NOT-A)A(因为B不是以public方式派生自A的,而是以private方式)。
B* pb4 = static_cast<B*>(&d1);这一句不错,但也没必要做转型,因为derived-to-base(由派生类到基类)的指针转换可以被隐式的完成。
D* pd = static_cast<D*>(pb4);这一句不错。如果你原先认为这里需要的是dynamic_cast的话,这或许会使你感到吃惊。其原因是,当目标已知的时候,向下转型(downcast)可以是静态的,此时要注意:你这样等于是在告诉编译器,你知道“被指针所指的正是那种型别”这个事实。如果你错了,那么这个转型将无法告知你已经出现的问题(dynamic_cast在转型失败时就能返回一个null pointer以告知你出现了问题),于是你此时至多也只能得到各种不同的运行期错误以及/或者程序崩溃。
pa1 = dynamic_cast<A*>(pb2); pa1 = dynamic_cast<A*>(pb4);这两句看起来很相似。两句都试图使用dynamic_cast来把B*转换为A*。然而,第一个是错误的而第二个是正确的。
原因是:正如前面所述,你不能使用dynamic_cast把一个指向B对象(这里pb2指向对象b1)的指针转换为指向A对象的指针,因为B是以private方式从A进行继承的,不是以public方式。然而第二句中的转型是成功的,这是因为pb4指向对象d1,而D(通过C)将A作为一个间接的public base class,从而让dynamic_cast可以沿着B*-->D*-->C*-->A*的路径在继承层次结构中进行转型。
C* pc1 = dynamic_cast<C*>(pb4);这一句也不错,其原因与上面的一样:dynamic_cast可以穿越继承层次进行交叉转型(cross-cast),因此这一句是合法的并可以成功执行。
C& rc1 = dynamic_cast<C&>(*pb2);最后这一句是错的……因为*pb2并不真的就是一个C,dynamic_cast会抛出一个bad_cast异常来报告失败。为什么?因为dynamic_cast可以在指针转型(pointer cast)失败时返回null,但由于没有null reference一说,因此当一个引用转型(reference cast)失败时便无法返回null reference。除了抛出一个异常以外,没有别的方法来报告错误了——标准的bad_cast异常类也就是因此而来的。
(完)