C++ 类型转换

C++中的const_cast、static_cast、dynamic_cast和reinterpret_cast

C++类型转换

典型的C类型转换如下所示

text
1
2
Typename_1 var_type_1;
Typename_0 var_type_0 = (Typename_0)var_type_1;

在C语言中,不管什么类型的转换都可以使用上述的形式;C++也支持上述类型的强制类型转换,但是这种转换可能会带来一些隐患,所以C++提供了四个在不同场合的强制类型转换函数:const_cast, static_cast, dynamic_cast, reinterpret_cast

一、const_cast

const_cast用于修改指针或者引用,将指针或引用的常量形式转为非常量的形式,并且仍然指向原来的对象

SUM:

  • 修改指针或者引用
  • 常量转为非常量

Code:

text
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 正常的使用方法
int test_func(){
   int num = 6;
   const int *p_const_num = #

   int *p_num = const_cast<int*>p_const_num;
   *p_num = 8;
   return 0;
}

# 未定义的使用方法
int test_func(){
   const char *p_const_str = "ABCD";

   char *p_str = const_cast<char*>p_const_str;
   return 0;
}

二、static_cast

static_cast与C语言风格的强制转换效果一样

SUM:

常用于:

  • 类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。注意:进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
  • 基本数据类型之间的转换,如把int转换成char,把int转换成enum。安全性需要开发者来维护

注:

  • 没有运行时检查,与C风格一样都存在安全隐患
  • static_cast不能转换掉原有类型的const、volatile、或者 __unaligned属性。(前两种可以使用const_cast 来去除)
  • c++ 的任何的隐式转换都是使用 static_cast 来实现

Code:

text
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Base
{
   // base class
};

class Sub : public Base
{
// sub class
};

// 继承类向基类的转换,编译通过并且是安全的
Sub sub;
Base *base_ptr = static_cast<Base*>sub;

// 基类向继承类的转换,编译通过但是是不安全的
Base base;
Sub *sub_ptr = static_case<Sub*>base;

三、dynamic_cast

dynamic_cast将基类指针(或引用)转换成继承类指针(或引用),dynamic_cast会根据基类指针是否真正指向继承类指针做相应处理。

text
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Base{
public:
   virtual void print(){
       cout << "base class print" << endl;
  }
};

class Sub : public Base {
public:
   void print() override{
       cout << "sub class print" << endl;
  }
};

int main() {
   Sub *sub_ptr = new Sub();
   sub_ptr->print();
   // 如果基类没有虚函数,使用dynamic_cast会报错
   Base *base_ptr = dynamic_cast<Base*>(sub_ptr);
   if(base_ptr){
       base_ptr->print();
  }else{
       cout << "base ptr is NULL" << endl;
  }
   delete sub_ptr;

   base_ptr = new Base();
   base_ptr->print();
   sub_ptr = dynamic_cast<Sub*>(base_ptr);
   if(sub_ptr){
       sub_ptr->print();
  }else{
       cout << "sub ptr is NULL" << endl;
  }
   delete base_ptr;
   return 0;
}
/*
   Win10上的Clion输出如下:
       sub class print
       sub class print
       base class print
       sub ptr is NULL
*/

从上边的运行结果可以看出:

  • 从子类到基类的指针dynamic_cast转换没有问题
  • 从基类到子类的转换虽然编译没有报错,但是转换的sub_ptr是一个空指针,说明dynamic_cast在程序运行时对类型进行了检查(RTTI,运行期类型检查,Runtime type information

这个检查主要来自虚函数,虚函数时dynamic_cast转换能够进行的前提条件。当一个类有一个虚函数,那么编译器会构造出来一个虚函数表来指示这些虚函数的地址,如果该类被继承并且子类实现了一个同名并具有相同的函数签名的方法重写了基类的方法,那么虚函数表中会将该函数指向新的地址。此时多态性体现:使用激烈的指针或引用指向子类的对象,调用该方法时会顺着虚函数表找到对应子类的方法。

四、reinterpret_cast

reinterpret_cast是强制类型转换符用来处理无关类型转换的,通常为操作数的位模式提供较底层的重新解释。

主要应用在:

  • 任意指针之间的转换
  • 引用之间的转换
  • 指针和足够大的int类型之间的转换
  • 整数到指针的转换
text
1
2
3
4
5
6
7
8
9
10
11
12
int main() {
   int *num_ptr = new int(2333);
   uint64_t ptr_num = reinterpret_cast<uint64_t>(num_ptr);
   cout << "ptr addr :" << hex << num_ptr << endl
       <<  "num val :" << hex << ptr_num << endl;
   return 0;
}

/*64位机器上的测试和输出
ptr addr :0x80004adc0
num val :80004adc0
*/

参考

【0】c++ 四种强制类型转换介绍

【1】C++虚函数表剖析

【2】dynamic_cast