Post

C++ 强制类型转换

继承自 C 语言的强制类型转换,存在以下不足:

  • 没法从形式上体现类型转换的目的或者说功能,让人一眼能看出来是什么用途,遇到问题也没法根据问题类型进行针对性搜索,也看不出来是否有风险,风险类型又是什么
  • 不做转换后的类型检查,安全由开发人员自行负责

于是 C++ 提供了四种不同的强制类型转换,方便根据不同情况选用不同的强制类型转换,方便阅读和排查问题,很多情况还都会触发编译报错或者运行时抛出异常。

编译期间处理:

  • static_cast
  • const_cast
  • reinterpret_cast

运行时处理:

  • dynamic_cast

1. static_cast

用于 C++ 内置类型,数值型(整型、浮点型)、字符型、布尔型之间的转换。

如果涉及到类的话,只能在有相互联系的类型中进行转换(并不是指虚函数多态),或者类中重载了到某个类型 T 的类型转换函数,如:

1
2
3
4
5
6
7
8
class A {
public:
    ...
    operator int() const { return val; }
    ...
private:
    size_t val;
};

类型 A 就可以被转换到 int

static_cast 的一些用法的情况:

  1. 用于基类和派生类指针或引用之间的转换
    • 上行转换,即派生类指针或引用转换成基类,是安全的
    • 下行转换,即基类指针或引用转换成派生类,由于没有动态类型检查,所以是不安全的
  2. 用于基本数据类型之间的转换,开发人员来保证安全性
  3. 把空指针转换成其他类型的空指针
  4. 把任何类型的表达式转换为 void 类型

static_cast 不能去除 constvolatile__unaligned 属性。

2. reinterpret_cast

可以做指针类型、引用类型、整型之间的转换,无关类型,只是逐比特复制,重新解释对象的比特模型,转换后的安全性就靠程序员自己负责了。

  1. 将指针或引用转换为一个足够长度的整型,不够长就丢失信息了
  2. 将整型转换为指针或引用类型
  3. 函数指针转换成另一个不同类型的函数指针
  4. 类的成员函数指针转成成另一个不同类型的成员函数指针
  5. 对象指针或引用转换成另一个不同类型的对象指针或引用
  6. 指向类数据成员的指针转换成指向另一个不同类型的数据成员的指针

3. const_cast

去除指针或引用的 constvolatile 限定,只能用于给指针、引用、数据成员的引用去除 const 限定,而不能是变量本身。

1
2
3
4
5
6
7
8
// 指针
int a = 10;
const int *p1 = &a;
int *p2 = const_cast<int *>(p1);
// 引用
int a = 10;
const int &p1 = a;
int &p2 = const_cast<int &>(p1);

4. dynamic_cast

dynamic_cast 专用于基类指针或引用与派生类指针或引用之间的转换,依赖于 C++ 多态机制,只能用于多态类,换言之必须是包含虚函数的基类和派生类之间的转换,否则无法通过编译,会报不是多态类的错误。

1
error: 'A' is not polymorphic

虽然 reinterpret_cast 也可以这么做,但不会做类型安全检查,dynamic_cast 会做检查,比较安全。

4.1. 上行转换

也就是从派生类指针转换成基类指针。

通常来说,如果你要向上转换,那你拿着的应该是一个派生类指针,那你写代码的时候就应该知道它的基类是什么了,应该是没必要动态转换的,IDE 应该会告诉你用 dynamic_cast 是多余的,直接赋值隐式转换就可以了,或者写个 static_cast 显式转一下(当然 IDE 也会告诉你这同样是多余的),效果都是一样的,都是安全的。

举个例子,A 是一个基类,有虚函数,B 是派生类,继承自 A。

1
2
B *pb = new B(2);
A *pa = dynamic_cast<A *>(pb);

4.2. 下行转换

也就是基类指针或引用到派生类指针或引用的转换。

dynamic_cast 具有类型检查的功能,比 static_cast 更安全。

转换后的指针或引用类型,需要和对象本身一致。这意思是说,假设一个基类指针本来指向一个派生类对象(这是没问题的,多态就这么用的),然后我们可以用 dynamic_cast 将这个基类指针转换成派生类指针,由于这个基类指针本身就是指向的一个派生类对象,所以可以安全地转换成对应的派生类指针。

1
2
3
4
A *pa = new B(2);
B *pb = dynamic_cast<B *>(pa);
cout << "pa = " << pa << endl;  // => pa = 0x600000ca8030
cout << "pb = " << pb << endl;  // => pb = 0x600000ca8030

反过来会转换失败。转换失败后,对于指针类型,返回 NULL,对于引用类型,抛出异常 std::bad_cast

1
2
3
A a(2);
A &ra = a;
B &rb = dynamic_cast<B &>(ra);  // => std::bad_cast
1
2
3
4
A *pa = new A(2);
B *pb = dynamic_cast<B *>(pa);  // 转换失败,返回 NULL
cout << "pa = " << pa << endl;  // => pa = 0x600002540030
cout << "pb = " << pb << endl;  // => pb = 0x0
This post is licensed under CC BY 4.0 by the author.