Guru of the Week 条款17:转型

    技术2022-05-11  178

    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_castconst_cast<A*>(&ra1);

          pa = (A*)&a2;

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

          pb = (B*)&c1;

    应该使用reinterpret_castpb=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。这一开始或许会使你感到吃惊;这样做的原因是,charsigned 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-AB(因为C不是以public方式派生自B的,且实际上压根儿就不是派生自B的)。这里唯一合法可用的转型就是reinterpret_cast,但使用它也几乎总是很龌龊的。

          A* pa3 = dynamic_cast<A*>(&b1);

    错误:这一句是非法的,因为b1不是一个(IS-NOT-AA(因为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并不真的就是一个Cdynamic_cast会抛出一个bad_cast异常来报告失败。为什么?因为dynamic_cast可以在指针转型(pointer cast)失败时返回null,但由于没有null reference一说,因此当一个引用转型(reference cast)失败时便无法返回null reference。除了抛出一个异常以外,没有别的方法来报告错误了——标准的bad_cast异常类也就是因此而来的。

    (完)


    最新回复(0)