Guru of the Week 条款17:转型

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
    
    
     
     (&c);
     
     
    
    
      signed char* psc = static_cast
    
    
     
     (&c);
     
     
    
    
      void* pv = static_cast
    
    
     
     (&b1);
     
     
    
    
      B* pb1 = static_cast
    
    
     
     (pv);
     
     
    
    
      B* pb2 = static_cast
    
    
     
     (&b1);
     
     
    
    
      A* pa1 = const_cast
    
    
     
     (&ra1);
     
     
    
    
      A* pa2 = const_cast
    
    
     
     (&ra2);
     
     
    
    
      B* pb3 = dynamic_cast
    
    
     
     (&c1);
     
     
    
    
      A* pa3 = dynamic_cast
    
    
     
     (&b1);
     
     
    
    
      B* pb4 = static_cast
    
    
     
     (&d1);
     
     
    
    
      D* pd = static_cast
    
    
     
     (pb4);
     
     
    
    
      pa1 = dynamic_cast
    
    
     
     (pb2);
     
     
    
    
      pa1 = dynamic_cast
    
    
     
     (pb4);
     
     
    
    
      C* pc1 = dynamic_cast
    
    
     
     (pb4);
     
     
    
    
      C& rc1 = dynamic_cast
    
    
     
     (*pb2);
     
     
    
    
  }

 

[解答]

 

1.  下列哪一种新风格的转型不能与C中的转型相对应?

 

只有dynamic_cast不能与C的转型相对应。其它的新风格转型都能与C中的旧风格转型相对应。

 

2.  对于下列每一个C中的转型语句,写出相应的新风格转型语句。其中哪一个语句如果不以新风格编写的话就是不正确的?

    void f() {
    
    
      A* pa; B* pb; C* pc;
    
    
      pa = (A*)&ra1;
    
    

应该使用const_castconst_cast (&ra1);

      pa = (A*)&a2;
    
    

这一句无法以新风格的转型表达。最接近的方案是使用const_cast,但a2是一个const object,语句执行的结果是未定义的。

      pb = (B*)&c1;
    
    

应该使用reinterpret_castpb=reinterpret_cast (&c1);

      pc = (C*)&d1;
    
    
}
    
    

这一转型在C中是错误的。而在C++中,并不需要转型:pc=&d1;

 

3.  评判下列每一条C++转型语句的编写风格和正确性。

 

首先要注意:我们并不知道本条款中给出的类是否拥有虚拟函数;如果涉及转型的那些类并不拥有虚拟函数,那么下述所有对dynamic_cast的使用都是错误的。在下面的讨论中,我们假设所有的类都拥有虚拟函数,从而使所有的dynamic_cast用法都合法。

    void g() {
    
    
      unsigned char* puc = static_cast
    
    
     
     (&c);
     
     
    
    
      signed char* psc = static_cast
    
    
     
     (&c);
     
     
    
    

错误:对两条语句我们都必须使用reinterpret_cast。这一开始或许会使你感到吃惊;这样做的原因是,charsigned char以及unsigned char是三个互不相同、区别开来的型别。尽管它们之间存在着隐式转换,它们也是互无联系的,因而指向它们的指针也是互无联系的。

      void* pv = static_cast
    
    
     
     (&b1);
     
     
    
    
      B* pb1 = static_cast
    
    
     
     (pv);
     
     
    
    

这两句都不错,但第一句中的转型是不必要的,因为本来就有从一个对象指针到void*的隐式转型动作存在。

      B* pb2 = static_cast
    
    
     
     (&b1);
     
     
    
    

这一句不错,但其转型也是不必要的,因为其引数(argument)已经是一个B*

      A* pa1 = const_cast
    
    
     
     (&ra1);
     
     
    
    

这一句是合法的,但是使用转型来去掉const-ness(常量性)是潜在的不良风格的体现。在大部分情况下,即当你因合理的缘由而想要去掉指针或引用的const-ness(常量性)时,这都涉及到某些类成员,并通常会使用mutable关键字来完成。请参看GotW#6了解更多关于const-correctness的讨论。

      A* pa2 = const_cast
    
    
     
     (&ra2);
     
     
    
    

错误:如果该指针被用来对对象施行写操作,那么就会产生未定义行为;因为a2是一个const object。要明白其原因,可以试想如果一个编译器了解到“a2是作为const object而被创建的”这个情况,并出于优化的考虑而将其存放在只读存储区,会发生什么事情。很明显,想通过转型而去掉这样一个对象的const属性是危险的。

 

注意:我并没有举例显示如何使用const_cast把一个non-const指针转型为一个const指针。因为这样做是多此一举;将一个non-const指针赋值给一个const指针,这本来就是合法的。我们只需要使用const_cast做相反的操作。

     B* pb3 = dynamic_cast
    
    
     
     (&c1);
     
     
    
    

错误(当你企图使用pb3时发生):这一句会将pb3设置为null,因为c1不是一个(IS-NOT-AB(因为C不是以public方式派生自B的,且实际上压根儿就不是派生自B的)。这里唯一合法可用的转型就是reinterpret_cast,但使用它也几乎总是很龌龊的。

      A* pa3 = dynamic_cast
    
    
     
     (&b1);
     
     
    
    

错误:这一句是非法的,因为b1不是一个(IS-NOT-AA(因为B不是以public方式派生自A的,而是以private方式)。

      B* pb4 = static_cast
    
    
     
     (&d1);
     
     
    
    

这一句不错,但也没必要做转型,因为derived-to-base(由派生类到基类)的指针转换可以被隐式的完成。

      D* pd = static_cast
    
    
     
     (pb4);
     
     
    
    

这一句不错。如果你原先认为这里需要的是dynamic_cast的话,这或许会使你感到吃惊。其原因是,当目标已知的时候,向下转型(downcast)可以是静态的,此时要注意:你这样等于是在告诉编译器,你知道“被指针所指的正是那种型别”这个事实。如果你错了,那么这个转型将无法告知你已经出现的问题(dynamic_cast在转型失败时就能返回一个null pointer以告知你出现了问题),于是你此时至多也只能得到各种不同的运行期错误以及/或者程序崩溃。

      pa1 = dynamic_cast
    
    
     
     (pb2);
     
     
    
    
      pa1 = dynamic_cast
    
    
     
     (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
    
    
     
     (pb4);
     
     
    
    

这一句也不错,其原因与上面的一样:dynamic_cast可以穿越继承层次进行交叉转型(cross-cast),因此这一句是合法的并可以成功执行。

     C& rc1 = dynamic_cast
    
    
     
     (*pb2);
     
     
    
    

最后这一句是错的……因为*pb2并不真的就是一个Cdynamic_cast会抛出一个bad_cast异常来报告失败。为什么?因为dynamic_cast可以在指针转型(pointer cast)失败时返回null,但由于没有null reference一说,因此当一个引用转型(reference cast)失败时便无法返回null reference。除了抛出一个异常以外,没有别的方法来报告错误了——标准的bad_cast异常类也就是因此而来的。

(完)




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值