c++中的四大类型强转

来源:互联网 发布:python try except e 编辑:程序博客网 时间:2024/04/29 05:03

C++的四种强制转换

        C++中的四种转换,是一个老生常谈的话题。但是对于初学者来说,该如何选择哪种转换方式仍然会有点困惑。而且我总是觉得“纸上得来终觉浅”,于是便“绝知此事要躬行”。于是利用闲暇时光,整理一下reinterpret_cast、const_cast、static_cast和dynamic_cast这四种强制转换的相关知识。(转载请指明出于breaksoftware的csdn博客)

        一般来说,我们需要类型转换的场景可以分为如下几种:

  • 整型和浮点型相互转换。这种转换往往是在数学计算的场景下。比如一个库函数导出的是double型数据,而我们使用该数据的函数的参数要求是整型,于是我们就需要对其进行转换。反之亦然。
  • 整型和指针相互转换。当我们试图根据某个成员变量的偏移位计算其在该对象内存空间位置时,就会需要将指针转换为整型进行计算。当计算出该变量的位置后(整型),就需要将其转换为指针类型。
  • 整型和枚举类型相互转换。这种转换往往发生在数学计算的场景下。因为枚举一般只是用于表意,而实际参与运算的还是整型数据。
  • 指针和无类型指针相互转换。一个典型的场景是,win32编程中,线程函数的入参要求是个LPVOID型数据。而我们往往将类对象的指针传递进去,以方便我们调用封装在类中的相关函数和变量。即CreateThread时将指针转为void*型,在线程函数中将void*转为指针。
  • 无关系类指针的相互转换。这种场景并不多见。
  • 存在继承关系的类指针相互转换。多发生在多态等场景下。
  • 转换不同长度的数据。这是个转换截断的问题,在现实使用中,也不难见到。

        在测试如上场景时,我们往往会遇到阻碍。这种阻碍来源于两个方面:

  • 编译器出错。这是因为语法规定这种使用不合法。所以编译器在编译代码时,认为该行为违法,终止之后的流程。
  • 运行时出错。这是因为在语法上是合法的,但是运行时是不合理的

        为了更好讨论如上场景,我们先预备一些辅助结构。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. class Parent {  
  2. public:  
  3.     Parent() {m_strPointerFrom = "Parent Pointer";};  
  4. public:  
  5.     void print() {printf("%s From Parent Func\n", m_strPointerFrom.c_str());};  
  6.     virtual void printv() {printf("%s From Parent Virutal Func\n", m_strPointerFrom.c_str());};  
  7. protected:  
  8.     std::string m_strPointerFrom;  
  9. };  
  10.   
  11. //#define USEERROR  
  12.   
  13. class Child : public Parent {  
  14.   
  15. #ifndef USEERROR  
  16.   
  17. public:  
  18.     Child() {m_strPointerFrom = "Child Pointer";};  
  19. public:  
  20.     void print() {printf("%s From Child Func.\n", m_strPointerFrom.c_str());};  
  21.     virtual void printv() override {printf("%s From Child Virutal Func.\n", m_strPointerFrom.c_str());};  
  22. #else  
  23.   
  24. public:  
  25.     Child() {m_strPointerFrom = "Child Pointer"; m_strOnlyChild = "OnlyInChild";};  
  26. public:  
  27.     void print() {printf("%s From Child Func. %s\n", m_strPointerFrom.c_str(), m_strOnlyChild.c_str());};  
  28.     virtual void printv() override {printf("%s From Child Virutal Func. %s\n", m_strPointerFrom.c_str(), m_strOnlyChild.c_str());};  
  29. private:  
  30.     std::string m_strOnlyChild;  
  31.   
  32. #endif  
  33.   
  34. };  
  35.   
  36. class Temp{};  

        Temp类是一个无继承关系的原始类。

        Child类继承于Parent类。

        Child类中print函数隐藏了Parent类中定义的print函数的实现。

        Child类也实现了Parent类中的虚方法printv。

        为了区分Parent和Child类的对象,我们设计了一个变量——m_strPointerFrom。这两个类的所有方法都将这个变量打印出来,以帮助我们在之后的测试中标识其来源。

        我们使用预定义控制了Child类的实现。我们可以先关注没有定义USEERROR这个宏的版本。另外一个版本我们将在后面介绍其设计意图。

        如果是编译期间出错,我将在给出的代码示例中,使用注释方法使该代码行失效。如果是运行期间出错,我们将任由之存在,但是会在最后点出其出错的地方。所以看本博文切不可“断章取义”。在引入C++四种转换之前,我们先看下最常见的一种转换——类C语言方式的转换。

类C类型的强制转换

        类c类型的强制转换是我们最常见的一种转换,比如:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int a = 0;  
  2. double b = (double)a;  

        我们列出这种方式,是为了让其和我们即将讨论的四种C++强制转换进行对比。根据之前设计的方案,我们列出如下代码:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void c_like_cast_test() {  
  2.   
  3.     {  
  4.         Parent* pParent = new (std::nothrow) Parent();  
  5.         if (!pParent) {  
  6.             return;  
  7.         }  
  8.   
  9.         int a = (int)(pParent);         // 指针向整型转换  
  10.         Parent* pParent1 = (Parent*)(a);// 整型向指针转换  
  11.   
  12.         double b = (double)(a);         // 整型向浮点型转换  
  13.         int a1 = (int)b;                // 浮点型向整型转换  
  14.   
  15.         void* pv = (void*)pParent;      // 指针向无类型指针转换  
  16.         Parent* pParent2 = (Parent*)pv; // 无类型指针向指针转换  
  17.         Temp* pTemp = (Temp*)pParent;   // 无关系类型指针相互转换  
  18.         Parent* pParent3 = (Parent*)pTemp;// 无关系类型指针相互转换  
  19.         ETYPE e = (ETYPE)a;             // 整型向枚举转换  
  20.         int a2 = (int)e;                // 枚举向整型转换  
  21.         int a3 = reinterpret_cast<int>(pv);                   // 无类型指针转整型  
  22.         Temp* pTemp1 = reinterpret_cast<Temp*>(pv);           // 无类型指针转其他指针  
  23.         delete pParent;}  
        可以见得类C的转换对如上四种相互转换并不存在编译问题。至于是否存在运行时问题,就要看我们对数据的预期和对相关指针的使用了。

        我们再看存在父子关系的指针的转换及不同长度类型数据转换的例子:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. {       // A  
  2.     Parent* pParent = new (std::nothrow) Parent();  
  3.     if (!pParent) {  
  4.         return;  
  5.     }  
  6.     pParent->print();  
  7.     pParent->printv();  
  8.     Child* pChild = (Child*)(pParent);  
  9.     pChild->print();               
  10.     pChild->printv();  
  11.     delete pParent;  
  12. }  
  13.   
  14. {       //B  
  15.     Child* pChild = new (std::nothrow) Child();  
  16.     if (!pChild) {  
  17.         return;  
  18.     }  
  19.     pChild->print();  
  20.     pChild->printv();  
  21.   
  22.     Parent* pParent = (Parent*)(pChild);  
  23.     pParent->print();                  
  24.     pParent->printv();  
  25.     delete pChild;  
  26. }  
  27.   
  28. {  
  29.     Child* pChild = new (std::nothrow) Child();  
  30.     if (!pChild) {  
  31.         return;  
  32.     }  
  33.     Temp* pTemp = (Temp*)pChild;  
  34.     delete pChild;  
  35. }  
  36.   
  37. {  
  38.     intptr_t p = 0xF000000000000001;  
  39.     Parent* pP = (Parent*)(p);  
  40.     printf("0x%x\n", pP);  
  41. }  
        从这段代码可以看出,我们没有将任何一行有效代码注释掉。这个说明如上的写法也不会导致编译期间出现问题——但是这并不意味着这样的代码就是正确的——父子指针转换可能会导致运行期出错。这个问题我们会在之后讨论。我们先看下执行的结果。

        上图中A、B区域和代码中的A、B区域相对照。由上面可以得出如下结论:

  • 调用的被隐藏函数实体(函数入口)是由其调用时的指针类型决定的——print函数是调用Child版(From Child Func)的还是Parent版(From Parent Func)?这是由其左侧指针类型决定的。如果左侧指针是Child指针类型,则调用的是Child的print。如果左侧指针是Parent指针类型,则调用的Parent的print。
  • 调用的虚函数实体(函数入口)是由对象的实际类型决定的——printv函数是调用Child版(From Child Virtual Func)还是Parent版(From Parent Virtual Func)?这是由调用者最初被new出来时的类型决定的。如果是调用new (std::nothrow) Child,则调用的是Child的printv。如果是调用new (std::nothrow) Parent,则调用的是Parent的printv。
  •  无论指针在被创建后如何转换,其指向的依旧是初始时new出来的对象——可以见得A区域中的指针都指向了Parent类对象(Parent Pointer),而B区域中的指针都指向了Child类的对象(Child Pointer)。

        说到这个问题,可能就要扯一点C++对象的内存模型。这儿我并不详细介绍其模型,只是想引出几个原理:

  • 类成员函数的实现,在内存中是有一个唯一入口和唯一代码片的。可以想象下,这段代码片和类数据是“分离”的,它们只是在编译期间由编译器保证其相关性。
  • 驱动类函数执行的是类的this指针所指向的数据区。其实类的非静态函数的第一个参数——也是隐藏的参数是这个类的this指针。通过该this指针,该函数才能访问到对象的成员数据。于是在多线程环境下,一个对象的函数在被多个线程执行时,它们会可能会修改同一个this指针的同一个数据。

        如果能正确理解如上两点,则上例中的结果便可以得到理解了。


        再回到类型转换上来。可以说类C的强制转换的能力是非常强大的,使用这种方法就意味着“通吃”。这也是大家非常喜欢使用它的一个原因。为什么它这么强大,我们看下汇编,以其中几段为例:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1.         Parent* pParent = (Parent*)(pChild);  
  2. 01077A5D  mov         eax,dword ptr [pChild]    
  3. 01077A60  mov         dword ptr [pParent],eax    
[plain] view plaincopy在CODE上查看代码片派生到我的代码片
  1.         int a = (int)(pParent);     // 指针向int型转换  
  2. 01077896  mov         eax,dword ptr [pParent]    
  3. 01077899  mov         dword ptr [a],eax    
        由上可见,它只是简单的二进制拷贝的过程。所以这种简单的实现,使之有强大的功能。但是也是这种简单的设计,使之使用也存在隐患,这个我们会在之后讨论。

reinterpret_cast

        reinterpret_cast是四种C++强制转换中和类C强制转换最接近的了。我们看一段来自cplusplus网站上对该转换的说明:

[plain] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /*  
  2. reinterpret_cast converts any pointer type to any other pointer type, even of unrelated classes.  
  3. The operation result is a simple binary copy of the value from one pointer to the other.  
  4. All pointer conversions are allowed: neither the content pointed nor the pointer type itself is checked.  
  5. It can also cast pointers to or from integer types.   
  6. The format in which this integer value represents a pointer is platform-specific.   
  7. The only guarantee is that a pointer cast to an integer type large enough to fully contain it (such as intptr_t),  
  8. is guaranteed to be able to be cast back to a valid pointer.  
  9. reinterpret可用于任何指针向任何指针的转换。它只是简单的进行二进制拷贝。  
  10. 它还可以用于将指针类型和整型类型相互转换(注意整型类型和指针类型的长度不一致)。  
  11. 它不进行类型检查。  
  12. */  
        从这段说明来看,其和类C转换没什么区别。但是我们的实验代码将证明它们还是存在不同之处的。
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void reinterpret_cast_test() {  
  2.     {  
  3.         Parent* pParent = new (std::nothrow) Parent();  
  4.         if (!pParent) {  
  5.             return;  
  6.         }  
  7.   
  8.         int a = reinterpret_cast<int>(pParent);               // 指针向整型转换  
  9.         Parent* pParent1 = reinterpret_cast<Parent*>(a);  // 整型向指针转换  
  10.   
  11. //      double b = reinterpret_cast<double>(a);               // 整型向浮点型转换  
  12. //      int a1 = reinterpret_cast<int>(b);                    // 浮点型向整型转换  
  13.   
  14.         void* pv = reinterpret_cast<void*>(pParent);      // 指针向无类型指针转换  
  15.         Parent* pParent2 = reinterpret_cast<Parent*>(pv); // 无类型指针向指针转换  
  16.   
  17.         Temp* pTemp = reinterpret_cast<Temp*>(pParent);       // 无关系类型指针相互转换  
  18.         Parent* pParent3 = reinterpret_cast<Parent*>(pTemp);// 无关系类型指针相互转换  
  19.   
  20. //      ETYPE e = reinterpret_cast<ETYPE>(a);             // 整型向枚举转换  
  21. //      int a2 = reinterpret_cast<int>(e);                    // 枚举向整型转换  
  22.   
  23.         int a1 = reinterpret_cast<int>(pv);                   // 无类型指针转整型  
  24.         Temp* pTemp1 = reinterpret_cast<Temp*>(pv);           // 无类型指针转其他指针  
  25.   
  26.         delete pParent;  
  27.     }  
        上述代码中,我们一共注释了4行。这四行是会在编译时出错的。所以我们可以见得reinterpret_cast不可用于浮点和整型之间的转换。也不可以用于枚举和整型的转换。但是可以发现,它还是很强大的的——因为它也是简单的二进制转换:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1.         Temp* pTemp = reinterpret_cast<Temp*>(pParent);       // 无关系类型指针相互转换  
  2. 013021DE  mov         eax,dword ptr [pParent]    
  3. 013021E1  mov         dword ptr [pTemp],eax    
  4.         Parent* pParent3 = reinterpret_cast<Parent*>(pTemp);// 无关系类型指针相互转换  
  5. 013021E4  mov         eax,dword ptr [pTemp]    
  6. 013021E7  mov         dword ptr [pParent3],eax    
        其它父子类指针的代码及长度不一致的转换就不贴出代码了,这些代码和类C强制转换差不多,只是将转换方式改了下。

        由上我们可以总结出:reinterpret_cast转换是在类C转换的基础上,在编译期间

  • 约束了整型、浮点型和枚举类型的相互转换。

        那么C++中有没有提供整型、浮点和枚举类型的相互转换方法呢?有的!见static_cast。

static_cast

        static_cast也是使用非常多的一种强制转换。我们看一段来自cplusplus网站上对该转换的说明:

[plain] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /*  
  2. static_cast can perform conversions between pointers to related classes,   
  3. not only upcasts (from pointer-to-derived to pointer-to-base),  
  4. but also downcasts (from pointer-to-base to pointer-to-derived).   
  5. No checks are performed during runtime to guarantee that the object being converted is in fact a full object of the destination type.   
  6. Therefore, it is up to the programmer to ensure that the conversion is safe.   
  7. On the other side, it does not incur the overhead of the type-safety checks of dynamic_cast.  
  8. 它用于在存在继承关系的类指针之间转换。可以从派生类指针转为基类指针,也可以从基类指针转为派生类指针。  
  9.   
  10. static_cast is also able to perform all conversions allowed implicitly  
  11. (not only those with pointers to classes), and is also able to perform the opposite of these.   
  12. It can:  
  13. Convert from void* to any pointer type. In this case,   
  14. it guarantees that if the void* value was obtained by converting from that same pointer type, the resulting pointer value is the same.  
  15. Convert integers, floating-point values and enum types to enum types.  
  16. 它可以将void*型向任意指针类型转换。还可以在整型、浮点型和枚举型将相互转换。  
  17. */  
        看了这个说明,似乎static_cast可以实现类C转换的所有场景了。但是实际不然

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void static_cast_test() {  
  2.   
  3.     {  
  4.         Parent* pParent = new (std::nothrow) Parent();  
  5.         if (!pParent) {  
  6.             return;  
  7.         }  
  8.   
  9. //      int a = static_cast<int>(pParent);                // 指针向整型转换  
  10. //      Parent* pParent1 = static_cast<Parent*>(a);       // 整型向指针转换  
  11.   
  12.         int a = 1;  
  13.         double b = static_cast<double>(a);                // 整型向浮点型转换  
  14.         int a1 = static_cast<int>(b);                 // 浮点型向整型转换  
  15.   
  16.         void* pv = static_cast<void*>(pParent);           // 指针向无类型指针转换  
  17.         Parent* pParent2 = static_cast<Parent*>(pv);  // 无类型指针向指针转换  
  18.   
  19. //      Temp* pTemp = static_cast<Temp*>(pParent);        // 无关系类型指针相互转换  
  20. //      Parent* pParent3 = static_cast<Parent*>(pTemp);   // 无关系类型指针相互转换  
  21.           
  22.         ETYPE e = static_cast<ETYPE>(a);              // 整型向枚举转换  
  23.         int a2 = static_cast<int>(e);                 // 枚举向整型转换  
  24.   
  25. //      int a3 = static_cast<int>(pv);                    // 无类型指针转整型  
  26.         Temp* pTemp = static_cast<Temp*>(pv);         // 无类型指针转其他指针  
  27.   
  28.         delete pParent;  
  29.     }  

        可以见得static_cast

  • 约束了指针和整型的相互转换。
  • 约束了无关系类型的指针的相互转换。(无类型指针除外)

        其他继承关系类指针相互转换也不列出了。其代码同类C相似,只是修改了操作方式。而且static_cast在汇编级的代码和类C强制转换是一致的。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1.         double b = (double)(a);         // 整型向浮点型转换  
  2. 01291872  fild        dword ptr [a]    
  3. 01291875  fstp        qword ptr [b]    
  4.         int a1 = (int)b;                // 浮点型向整型转换  
  5. 01291878  fld         qword ptr [b]    
  6. 0129187B  call        @ILT+420(__ftol2_sse) (12911A9h)    
  7. 01291880  mov         dword ptr [a1],eax    
  8.   
  9.         void* pv = (void*)pParent;      // 指针向无符号指针转换  
  10. 01291883  mov         eax,dword ptr [pParent]    
  11. 01291886  mov         dword ptr [pv],eax    
  12.         Parent* pParent2 = (Parent*)pv; // 无符号指针向指针转换  
  13. 01291889  mov         eax,dword ptr [pv]    
  14. 0129188C  mov         dword ptr [pParent2],eax  
        上面代码是类C的强制转换,下面的是static_cast的。我将整型和浮点相互转换的反汇编代码也提了出来,可以见得也是一样的。
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1.         double b = static_cast<double>(a);                // 整型向浮点型转换  
  2. 012928CD  fild        dword ptr [a]    
  3. 012928D0  fstp        qword ptr [b]    
  4.         int a1 = static_cast<int>(b);                 // 浮点型向整型转换  
  5. 012928D3  fld         qword ptr [b]    
  6. 012928D6  call        @ILT+420(__ftol2_sse) (12911A9h)    
  7. 012928DB  mov         dword ptr [a1],eax    
  8.   
  9.         void* pv = static_cast<void*>(pParent);           // 指针向无符号指针转换  
  10. 012928DE  mov         eax,dword ptr [pParent]    
  11. 012928E1  mov         dword ptr [pv],eax    
  12.         Parent* pParent2 = static_cast<Parent*>(pv);  // 无符号指针向指针转换  
  13. 012928E4  mov         eax,dword ptr [pv]    
  14. 012928E7  mov         dword ptr [pParent2],eax    

        说完上述两种转换,我们可以发现:在reinterpret_cast上,没有任何面向对象的痕迹。而static_cast出现了对继承的关系的约束。之后我们将介绍C++特性更强的转换——dynamic_cast。

dynamic_cast

        在讨论dynamic_cast之前,我们要先回到最前面定义的两个辅助类——Parent和Child上。之前为了保证这两个类指针在相互转换后,调用相关函数不会出现运行时错误,我们没有定义USEERROR宏。现在我们要开启USERROR宏,使得Child类比Parent类多一个成员变量——m_strOnlyChild。并在Child类重写函数print和继承的虚函数printv中使用到该变量。于是我们之前的类C强制转换、reinterpret_cast和static_cast对父子类指针转换后函数调用,将出现运行时出错。我们以static_cast为例:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. {  
  2.     Parent* pParent = new (std::nothrow) Parent();  
  3.     if (!pParent) {  
  4.         return;  
  5.     }  
  6.     pParent->print();  
  7.     pParent->printv();  
  8.     Child* pChild = static_cast<Child*>(pParent);  
  9.     pChild->print();                           
  10.     pChild->printv();  
  11.     delete pParent;  
  12. }  
        代码运行到pChild->print()时将出错。根据我们之前介绍的C++对象模型的知识,我们可以知道pChild指针仍然指向的是一个Parent对象。因为print是被隐藏函数,其左侧指针pChild是Child*型,所以编译器会将调用指向Child的print函数入口。而Child的print函数需要的成员变量m_strOnlyChild只在Child对象中存在,而不在Parent对象内存空间中。所以运行时会报非法地址访问之类的错误。

        在知道之前我们父类对象向子类指针转换的过程存在如此不安全的行为时,我们就要介绍dynamic_cast了。它有着很强烈的C++特性。我们先看下说明:

[plain] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /*  
  2. dynamic_cast can only be used with pointers and references to classes (or with void*).   
  3. Its purpose is to ensure that the result of the type conversion points to a valid complete object of the destination pointer type.  
  4. This naturally includes pointer upcast (converting from pointer-to-derived to pointer-to-base), in the same way as allowed as an implicit conversion.  
  5. But dynamic_cast can also downcast (convert from pointer-to-base to pointer-to-derived) polymorphic classes (those with virtual members)  
  6. if -and only if- the pointed object is a valid complete object of the target type.  
  7.   
  8. dynamic_cast can also perform the other implicit casts allowed on pointers: casting null pointers between pointers types (even between unrelated classes),  
  9. and casting any pointer of any type to a void* pointer.  
  10. dynamic_cast只可以用于指针之间的转换,它还可以将任何类型指针转为无类型指针,甚至可以在两个无关系的类指针之间转换。  
  11. */  
        由上可以知道,dynamic_cast在编译层约束了非指针类型的转换。于是我们的测试代码中很多被注释掉

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. class TmpBase{ publicvirtual void fun(){};};  
  2. class TmpDerived : public TmpBase{publicvirtual void fun() override {};};  
  3.   
  4. void dynamic_cast_test() {  
  5.     {  
  6.         Parent* pParent = new (std::nothrow) Parent();  
  7.         if (!pParent) {  
  8.             return;  
  9.         }  
  10.   
  11. //      int a = dynamic_cast<int>(pParent);               // 指针向整型转换  
  12. //      Parent* pParent1 = dynamic_cast<Parent*>(a);  // 整型向指针转换  
  13.   
  14. //      double b = dynamic_cast<double>(a);               // 整型向浮点型转换  
  15. //      int a1 = dynamic_cast<int>(b);                    // 浮点型向整型转换  
  16.   
  17.         void* pv = dynamic_cast<void*>(pParent);      // 指针向无符号指针转换  
  18. //      Parent* pParent2 = dynamic_cast<Parent*>(pv); // 无符号指针向指针转换  
  19.   
  20.         Temp* pTemp = dynamic_cast<Temp*>(pParent);       // 无关系类型指针相互转换  
  21. //      Parent* pParent3 = dynamic_cast<Parent*>(pTemp);// 无关系类型指针相互转换  
  22.   
  23. //      ETYPE e = dynamic_cast<ETYPE>(a);             // 整型向枚举转换  
  24. //      int a2 = dynamic_cast<int>(e);                    // 枚举向整型转换  
  25.   
  26. //      int a2 = dynamic_cast<int>(pv);                   // 无符号指针转整型  
  27. //      Temp* pTemp1 = dynamic_cast<Temp*>(pv);           // 无符号指针转其他指针  
  28.   
  29.         TmpDerived* pTmp2 = dynamic_cast<TmpDerived*>(pParent);  
  30.         Child* pChild1 = dynamic_cast<Child*>(pTmp2);  
  31.   
  32.         delete pParent;  
  33.     }  
        这段代码最有意思的是后面的两个转换

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. TmpDerived* pTmp2 = dynamic_cast<TmpDerived*>(pParent);  
  2. Child* pChild1 = dynamic_cast<Child*>(pTmp2);  
        dynamic_cast将两个无关系的类指针进行了转换,而且没有出现编译错误。我们看到TmpDerived类继承于TmpBase类,且TmpBase类中存在虚函数。这样设计的原因,是为了保证dynamic_cast操作的指针是具有多态特性。否则编译器会报错。我们看下其汇编便可知

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1.         TmpDerived* pTmp2 = dynamic_cast<TmpDerived*>(pParent);  
  2. 00D52D16  push        0    
  3. 00D52D18  push        offset TmpDerived `RTTI Type Descriptor' (0D5E034h)    
  4. 00D52D1D  push        offset Parent `RTTI Type Descriptor' (0D5E000h)    
  5. 00D52D22  push        0    
  6. 00D52D24  mov         eax,dword ptr [pParent]    
  7. 00D52D27  push        eax    
  8. 00D52D28  call        @ILT+885(___RTDynamicCast) (0D5137Ah)    
  9. 00D52D2D  add         esp,14h    
  10. 00D52D30  mov         dword ptr [pTmp2],eax   
        dynamic_cast在底层并不像之前的介绍的几种转换方法使用简单的内存拷贝,而是使用了RTTI技术,所以它要求操作的指针是多态的。因为上例中两个类不存在继承关系,所以每个转换操作都是失败的——返回Null。这样的特性就要求我们在使用dynamic_cast时,需要对返回结果判空,否则就会出现空指针问题。而带来的好处是,我们将避免之前遇到的运行时出错的场景——这个场景排查起来相对困难些。

        我们看段使用dynamic_cast规避掉运行时出错的代码

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. {  
  2.     Parent* pParent = new (std::nothrow) Parent();  
  3.     if (!pParent) {  
  4.         return;  
  5.     }  
  6.     pParent->print();  
  7.     pParent->printv();  
  8.     Child* pChild = dynamic_cast<Child*>(pParent);  
  9.     if (pChild) {                                       // Bad  
  10.         pChild->print();                           
  11.         pChild->printv();  
  12.     }  
  13.     else {  
  14.         printf("dynamic_cast error\n"); // Hit  
  15.     }  
  16.   
  17.     try {  
  18.         Child& c = dynamic_cast<Child&>(*pParent);        // Bad  
  19.     }  
  20.     catch (std::bad_cast& e) {  
  21.         printf("dynamic_cast error : %s\n", e.what());  // Hit  
  22.     }   
  23.   
  24.     delete pParent;  
  25. }  
        根据之前介绍的知识,可以得知我们创建的是Parent对象。因为将Parent对象转换为Child指针存在潜在的安全问题。dynamic_cast将会对这次操作返回Null。以保证我们代码的运行安全性。这儿有个需要指出的是,如果我们使用dynamic_cast转换成一个引用对象,如果出错,将是抛出异常。如果不做异常捕获,将导致我们程序崩溃。

        下面两段代码是安全的

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1.     {  
  2.         Child* pChild = new (std::nothrow) Child();  
  3.         if (!pChild) {  
  4.             return;  
  5.         }  
  6.         pChild->print();  
  7.         pChild->printv();  
  8.         Parent* pParent = dynamic_cast<Parent*>(pChild);  
  9.         if (pParent) {                                      // Well  
  10.             pParent->print();                  
  11.             pParent->printv();  
  12.         }  
  13.         else {  
  14.             printf("dynamic_cast error\n");  
  15.         }  
  16.   
  17.         try {  
  18.             Parent& p = dynamic_cast<Parent&>(*pChild);       // Well  
  19.             p.print();                
  20.             p.printv();  
  21.         }  
  22.         catch (std::bad_cast& e) {  
  23.             printf("dynamic_cast error : %s\n", e.what());  // No Hit  
  24.         }   
  25.   
  26.         delete pChild;  
  27.     }  
  28.   
  29.     {  
  30.         Parent* pChild = new (std::nothrow) Child();  
  31.         if (!pChild) {  
  32.             return;  
  33.         }  
  34.         pChild->print();  
  35.         pChild->printv();  
  36.         Parent* pParent = dynamic_cast<Parent*>(pChild);  
  37.         if (pParent) {                                      // Well  
  38.             pParent->print();                  
  39.             pParent->printv();  
  40.         }  
  41.         else {  
  42.             printf("dynamic_cast error\n");  
  43.         }  
  44.   
  45.         try {  
  46.             Parent& p = dynamic_cast<Parent&>(*pChild);       // Well  
  47.             p.print();                
  48.             p.printv();  
  49.         }  
  50.         catch (std::bad_cast& e) {  
  51.             printf("dynamic_cast error : %s\n", e.what());  // No Hit  
  52.         }   
  53.   
  54.         delete pChild;  
  55.     }  
  56. }  
        一般来说,因为RTTI技术作用于运行时,所以其会产生运行时的代价。所以很多人建议,如果在能明确转换安全的场景下,不要使用dynamic_cast方法进行转换,而是使用static_cast,以免进行一些不必要的运行时计算。

const_cast

        const_cast与之前介绍的各种转换都不同,它只是负责给变量增加或者删除一些属性,以保证编译器可以编过。我们直接看例子

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void printf_without_const(char* pbuffer){  
  2.     if (!pbuffer || 0 == strlen(pbuffer)) {  
  3.         return;  
  4.     }  
  5.     *(pbuffer) = '$';                       // can modify  
  6.     printf(pbuffer);  
  7. };  
  8.   
  9. void const_cast_test() {  
  10.   
  11.     {  
  12.         const char* pbuffer = "const_cast_test";  
  13.         char szbuf[64] = {0};  
  14.         strncat(szbuf, pbuffer, strlen(pbuffer));  
  15.   
  16.         const char* psz = const_cast<const char*>(szbuf);  
  17.         //printf_without_const(psz);            // error  
  18.   
  19.         char* p = const_cast<char*>(psz);  
  20.         printf_without_const(p);  
  21.     }  
  22. }  
        总结一下:
  • 类C强制转换十分强大。因为它是二进制级别内存拷贝操作,所以可以大部分场景不会出现编译错误。但是如果用它去转换指针,可能会出现运行时错误
  • reinterpret_cast、static_cast和dynamic_cast主要用于指针的转换
  • reinterpret_cast的能力仅次于类C转换。虽然它约束了整型、浮点和枚举类型的相互转换,但是还是支持指针和整型的转换。它也存在转换后运行时出错的隐患
  • static_cast弥补了reinterpret_case对整型、浮点和枚举类型的相互转换的功能。除了这些转换外,它要求操作的参数是指针。且已经出现C++特性限制,要求指针转换时的类存在继承关系(void*除外)。它也存在转换后运行时出错的隐患
  • dynamic_cast已经是纯的C++特性转换,使用到了RTTI技术。于是它要求操作的指针类型具有多态特性。它解决了指针转换后使用出现运行时出错的问题,但是使用该方法要付出运行时计算的代价如果能明确转换是安全的,建议使用static_cast方法(不使用reinterpret_cast是因为它还没体现出C++的特性)。
  • const_cast用于增加和去掉一些类型描述标志,如const等。

0 0