显式类型转换到底是个啥

之前的一篇隐式类型转换,介绍了编译器在什么情况下会偷偷的进行类型转换,以及转换的一些规则,这篇文章主要总结显式类型转换的一些知识

简单回顾一下,显式的类型转换有哪些,C++中提供4种强制类型转换的方式

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast

接下来就对4种强制类型转换的方式分别进行分析

static_cast

任何具有明确定义的类型转换,都可以使用 static_cast, 但是 static_cast 不能转换掉底层 constvolatile 属性

注意注意,static_cast 由于不提供运行时的检查,因此需要开发者自己确保转换的安全性,否则可能产生未定义的后果。

主要在以下几个场景中使用比较多:

  • 当需要把一个较大的算数类型赋值给较小的类型时,明确告诉了编译器我们不在乎潜在的精度损失,编译器也不会给出警告信息
  • void指针和具体类型指针之间的转换, static_cast 可以找回原本存在于 void* 指针中的值, 当然也可以转换成 void* 类型
  • 原有一些隐式类型转换, 例如向上转型, shortint, intdouble 等, 类类型转换(转换构造函数或者类型转换运算符)等等, 当然了可以不写 static_cast, 编译器也会默认转换的
  • 派生类向基类的转换,注意 static_cast 是把子类对象的指针或引用转换成父类指针或引用,这种转换是安全的,但是进行下行转换,是不安全的

dynamic_cast

dynamic_cast 用于在类的继承层次之间进行类型转换,它既允许向上转型,也允许向下转型

dynamic_cast 运算符的使用形式如下所示:

// type 必须是一个类类型
dynamic_cast<type*>(e)       // e 必须是一个有效的指针
dynamic_cast<type&>(e)       // e 必须是一个左值
dynamic_cast<type&&>(e)      // e 不能是左值

e 的类型必须符合以下三个条件中的任意一个

  • 目标 type 的公有派生类
  • 目标 type 的公有基类
  • 目标 type 的类型

如果符合,则类型转换成功。否则,转换失败

  • 如果一条 dynamic_cast 语句的转换目标是指针类型并且失败了, 则结果是 0
  // bp 指向一个 Base 对象
  // Base 基类, Derived 为其派生类
  if (Derived * dp = dynamic_cast<Derived> (bp)) {
      // 使用 dp 指向的 Derived 对象
  } else {
      // 使用 bp 指向的 Base 对象
  }
  • 如果转换目标是引用类型并且失败了,则 dynamic_cast 运算符会抛出一个 bad_cast 异常
  try {
      const Derived &d = dynamic_cast<const Derived&> d;
      // 使用 b 引用的 Derived 对象
  } catch(bad_cast) {
      // 处理类型转换失败的情况
  }

简单整理一下 dynamic_cast

  • 相比 static_castdynamic_cast 会在运行时检查类型转换是否合法,具有一定的安全性
  • dynamic_cast 转换仅适用于指针或引用。
  • 类的继承层次之间进行类型转换,有个前提是,基类必须要有虚函数,因为 dynamic_cas t是运行时类型检查 RTTI,需要运行时类型信息,而这个信息是存储在类的虚函数表中,只有一个类定义了虚函数,才会有虚函数表
    • 向上转型,一定能转换成功,因为向上转型始终是安全的
    • 向下转型,对于下行转换,dynamic_cast 是安全的(当类型不一致时,转换过来的是空指针),而 static_cast 是不安全的
    • 但是在多层继承的情况下,向下转型可能不一定能转换成功(不是说不安全),这就涉及到 C++ RTTI 机制下对象内存方面的知识,这里先不展开,后面会单独写一篇这方面的知识

const_cast

const_cast 用于移除类型的 constvolatile 属性,也是只能改变运算对象的 constvolatile 属性

  • 常量指针被转换成非常量指针,并且仍然指向原来的对象;
  • 常量引用被转换成非常量引用,并且仍然引用原来的对象。

reinterpret_cast

reinterpret_cast 通过 重新解释 底层位模式 的类型间转换, 简单说就是是从底层对数据类型进行重新解释

int *ip;
char *pc = reinterpret_cast<char*>(ip);

pc 所指的真实对象是一个 int 而非字符,所以把 pc 当成普通字符指针使用就可能在运行时发生错误

string str(pc);   // 非常危险

reinterpret_cast 相当于告诉编译器 pc 的值是 char* 类型,而实际上它存放的是指向 int 的指针

所以 reinterpret_cast 是非常激进的指针类型转换,在编译期完成,可以转换任何类型的指针,所以极不安全。不到万不得已,不用使用这个转换符

看到一篇博客里提到很有意思的一点:

reinterpret_cast 体现了 C++ 语言的设计思想:用户可以做任何操作,但要为自己的行为负责。

总结

强制类型转换干扰了正常的类型检查,因此我们应该避免使用强制类型转换, 尤其是 reinterpret_cast, 如果实在无法避免,也应该尽量限制类型转换的作用域,并且记录对相关类型的所有嘉定,这样可以减少错误的发生