Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

关于强制类型转换的小结 #55

Open
nine-point-eight-p opened this issue Jun 13, 2022 · 0 comments
Open

关于强制类型转换的小结 #55

nine-point-eight-p opened this issue Jun 13, 2022 · 0 comments

Comments

@nine-point-eight-p
Copy link

强制类型转换

问题引入

C/C++中的类型转换(type cast)可以分为自动(隐式)的类型转换和强制(显式)的类型转换,这里我们主要讨论显式转换。类型转换始终是一个需要慎重对待的问题。在C中,进行强制转换没有任何限制,形式上只需要在变量前面加上变量类型即可。但是这很容易产生奇怪的问题。例如:

#include<iostream>
struct Data
{
    double data[200];
};
struct Junk
{
    int junk[100];
};
int main()
{
    Data d = {2.5e33, 3.5e-19, 20.2e32}; // initialize
    char * pch = (char *) (&d);          // type cast #1 – convert to string
    char ch = char (&d);                 // type cast #2 - convert address to a char
    Junk * pj = (Junk *) (&d);           // type cast #3 - convert to Junk pointer
    std::cout << *(pj->junk);            // ???
    return 0;
}

这三个转换究竟有什么意义?输出结果又是什么?自定义类之间随意转换、指针之间随意转换、const和非const也随意转换……可见是过于随意了。C++既然要对类型的要求如此严格,就不应该允许这些转换的存在。因此,C++提供了4种类型转换操作符(type cast operator),以期进行合理、规范的强制转换。

类型转换操作符

4种类型转换操作符分别是:static_castdynamic_castconst_cast以及reinterpret_cast。它们的使用形式是相同的,即some_cast<new_type>(expression)。下面逐个做一小结。

static_cast

static_cast可以将expression转换为指定的new_type类型。如果不能转换,则会在编译时报错。

尽管看似接近于传统的C语言里的强制转换,但实际上增加了不少限制。从功能来说,大体可以分为以下几类:

  • 类的层次结构:在类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。其中,进行向上转换(upcast,把派生类的指针或引用转换成基类表示)是安全的;进行向下转换(downcast,把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,无法判定所指对象是否属于该派生类,所以是不安全的。

  • 一般数据类型间转换:基本上,只要存在从expression类型到new_type类型的隐式转换路径,就可以进行转换。这不同于C语言中的强制转换,因为static_cast毕竟还是提出了一些要求。比如,自定义的类一般需要有相应的构造/赋值函数才能进行转换;即便是内置数据类型,也不能实现char*int这种通常情况下没有意义的转换,哪怕char*int*都不行。这在一定程度上增加了安全性。

    特别地,void*可以转换为任意指针类型,任意数据类型也可以转换为void,同时整数类型和enum类型之间也存在转换。

  • 左值与右值:将左值等转换为右值引用。std::move的实现用到了这一点。

小结:static_cast在传统的C语言的强制转换上更进一步,在编译时进行静态类型检查,所以使用static_cast可以尽量发挥编译器的作用,减少强制转换带来的问题。然而,static_cast安全性一般仍需使用者自行保证,因为静态检查不能保证代码一定”正确“。可能的问题常常是来自于运行时,例如:向下转换导致访问错误,从void*转换到错误的指针类型等等。

dynamic_cast

dynamic_cast只能用于对象指针或引用之间的转换。具体来说,new_type只能是指针/引用/void*,而expression只能相应取指针/类的左值。

转换的结果有以下几种:

  • 如果转换成功,dynamic_cast返回new_type类型的值;
  • 如果转换失败:
    • new_type是指针类型,则返回空指针;
    • new_type是引用类型,则会抛出一个std::bad_cast类异常。

提到dynamimc_cast就少不了RTTI(Run-Time Type Information)。dynamic_cast的具体功能包括:

  • 涉及RTTI:dynamic_cast的核心——向下转换(downcast)。在多继承的层次结构下也可以进行sidecast,参见参考资料8。

  • 无关RTTI:这些基本上都可以由隐式转换或static_cast实现,(expression与new_type为同一个类时)添加cv标记,向上转换,转换空指针,向void*转换。

小结:dynamic_cast是4种转换里唯一一个具有运行时动态检查的强制转换。类的层次结构间进行向上转换时,dynamic_caststatic_cast的效果相同;在进行向下转换时,dynamic_cast增加了类型检查,比static_cast更安全。

const_cast

const_cast一般用来添加或移除const/volatile标记(简称cv标记,cv-qualifier),但可能并不如你所想的那样工作(比如const int a = 2; int b = const_cast<int>(a)?)。事实上,const_cast只能处理指针和引用,而且只能调整cv标记,不能更改类型(比如从intdouble)。具体来说,其功能包括:

  • 将左值转换为左值引用或右值引用,可以添加/移除cv标记;
  • 将右值(prvalue、xvalue)转换为右值引用,可以添加/去除cv标记。

注意:

  • const_cast不能处理函数指针或类的成员函数指针。

  • 可以通过const_cast绕过编译器的限制,更改const对象的值。例如:

    const int i = 3;
    *(const_cast<int*>(&i)) = 2;

    这是一个未定义行为(undefined behavior),也就是说无法知道会产生什么样的结果。

小结:const_cast可以用来添加/移除cv标记,但是也存在限制。主要用于函数传参(函数要求传const参数,而实参非const,因而强制转换)。

reinterpret_cast

reinterpret_cast正如其名,只是告知编译器重新解释类型,并不会产生任何实际运行时的指令。

reintepret_cast 的转换是发生在二进制位上的。转换前后,对象的二进制数据未发生任何变化,只是对这些二进制位的编译器类型标识发生了变化,或者说是编译器看待这些二进制位的结论不同了。“重新解释”的特性使得它可以完成很多前三种强制转换不能做到的事情,例如在整数和指针之间相互转换,但这也使得它异常危险,因此这里也不多做介绍了,可以参见参考资料。

小结:reinterpret_cast的作用其实就是明确告知编译器,这次转换在编译时放弃严苛的编译器类型检查,正确性由使用者自己保证,某种意义上就是退回C中的强制转换。当然,reinterpret_cast并不是C中的强制转换,比如它不能去掉cv标记等。reinterpret_cast转换在以上几种转换中最为危险,需要非常谨慎。

总结

个人认为,C/C++作为强类型语言,应该避免大量使用强制转换,否则就违背了语言设计的初衷。然而出于种种原因,我们的确有不得不用的时候。C中一般用隐式转换或强制类型转换解决,本质上是一种一刀切方案,全靠程序员自行把控。C++中的4种类型转换操作符实际上是细分了具体场景,让程序员在具体情况下显式使用相应的操作符来转换,一方面明晰语义,另一方面让编译器和运行时系统尽可能提供帮助,提高程序的可靠性。

参考资料

  1. C++ Primer Plus, Chapter 15, Type Cast Operators.
  2. C++中类型转换详解_K丶WO的博客-CSDN博客_c++类型转换
  3. [C++学习笔记8 - static_cast、reintepret_cast、const_cast、dynamic_cast、auto、decltype__Amen的博客-CSDN博客](https://blog.csdn.net/qq_38958704/article/details/124025692)
  4. [static_cast conversion - cppreference.com](https://en.cppreference.com/w/cpp/language/static_cast)
  5. [dynamic_cast conversion - cppreference.com](https://en.cppreference.com/w/cpp/language/dynamic_cast)
  6. [const_cast conversion - cppreference.com](https://en.cppreference.com/w/cpp/language/const_cast)
  7. [reinterpret_cast conversion - cppreference.com](https://en.cppreference.com/w/cpp/language/reinterpret_cast)
  8. [what is side-cast or cross-cast in Dynamic_cast in C++ - Stack Overflow](https://stackoverflow.com/questions/35959921/what-is-side-cast-or-cross-cast-in-dynamic-cast-in-c)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant