本文主要分析C++的4种显式类型转换
文章目录
- static_cast
- 基本数据类型转换
- const
- 用于类层次结构
- void*
- dynamic_cast
- 继承中的转换
- const_cast
- reinterpret_cast
- 参考资料
我们使用显示类型转换,就是在告诉编译器要怎么解释这块内存。
在早期C/C++中,显式的类型转换有如下两种形式
type(expr); // 函数形式的显式类型转换 (type)expr; // C语言风格
C++的新式显式类型转换
cast-name
(expression); - cast-name 有 static_cast、dynamic_cast、const_cast 和 reinterpret_cast 四种,表示转换的方式
- type表示转换的目标类型
- expression 是被转换的值
下面详细讲解下这四种显式类型转换
static_cast
格式为 static_cast
(expression) 任何在编写程序时能够明确的类型转换都可以使用 static_cast:
基本数据类型转换
基本数据类型转换,比如将int转换为double,反之亦然,需要编写程序时确认安全性
将 `int` 转换为 `double` 编译器可能的操作如下(VS2022 x64),比如有如下代码
int x = 0; double y = static_cast
(x); 其汇编码为,cvtsi2sd 指令把源地址1个双字有符号整数变成1个双精度浮点数,xmm0 为英特尔处理器中的一个128位寄存器,movsd mmword ptr [y],xmm0 指令表示从 xmm0 寄存器中移动低64位到 y 地址
const
将非const对象转换为const对象 (但不能将const对象转换为非const对象,这个只有const_cast才能做到)
如果转换底层 const 对象到非 const 对象,编译器会报表达式必须是可修改的左值错误
顶层 const 也不可以被 static_cast 转换
用于类层次结构
父类和子类之间指针和引用的转换
假设我们现在有两个类,父类 A 和其派生类 B
class A { public: virtual void foo() { std::cout << "A::foo()" << std::endl; } int m_a = 0; }; class B : public A { public: virtual void foo() { std::cout << "B::foo()" << std::endl; } int m_b = 0; };
- 进行上行转换,即把子类对象的指针/引用转换为父类指针/引用,这种转换是安全的
B b; static_cast(b).foo(); B* bptr = new B; static_cast(bptr)->foo();
输出如下
- 进行下行转换,即把父类对象的指针/引用转换成子类指针/引用,这种转换是不安全的,因为没有运行时检查,所以需要在编写程序时确认安全性
A* a = new A; std::cout << static_cast(a)->m_b << std::endl; // 不安全的,访问了申请外的内存
输出如下
void*
把 void* 转换为目标类型的指针(这是极其不安全的),类似于上面父类指针转子类指针,可能会访问申请外的内存
void* a = new A; std::cout << static_cast(a)->m_b << std::endl; // 不安全的,访问了申请外的内存
输出如下
dynamic_cast
格式为 dynamic_cast
(expression) 相比static_cast,dynamic_cast 会在运行时检查类型转换是否合法,具有一定的安全性。由于运行时的检查,所以会额外消耗一些性能。需要注意的是 dynamic_cast 转换仅适用于指针或引用。
继承中的转换
- 上行转换
由子类指针/引用转换到父类指针/引用,与 static_cast 和隐式转换一样,都是安全的
B* b = new B; A* a = dynamic_cast(b); std::cout << a->m_a << std::endl;
可以看到下面的汇编代码中,只是把指针的值给到了 [a] 地址
- 下行转换
由父类指针/引用转换到子类指针/引用,使用 dynamic_cast 进行下行转换有个条件,就是其操作数必须含有多态类类型,简单理解,父类得有虚函数。
如果我们将之前 类A 和 类B 中的虚函数去掉,则会报错
如果和之前一样,类 A 中有虚函数 foo(),那么执行 dynamic_cast 会发生什么
A* a = new A; B* b = dynamic_cast(a); std::cout << b->m_b << std::endl;
可以看到把类 B 和类 A 的运行时类型描述符放到了 r9 和 r8 寄存器中
之后运行时动态类型转换,这里的实现原理简单描述为,我们在虚函数表 vtbl[-1] 的位置存放着指向类型信息的 tpye_info*,那么由于我们实际指向的对象的类是 A 其取出的 RTTI Type Descriptor 应该是类 A 我们和需要转换的类 B 的 RTTI Type Descriptor 比较,然后发现不匹配,此时转换失败,会返回一个 nullptr。
如果 a 实际指向的是类 B 的对象,那么运行时转换成功。
注意转换的是引用,转换失败会抛出 bad_cast 异常
const_cast
格式为 const_cast
(expression) const_cast 用于移除类型的 const、volatile 和 _unaligned 属性
const A* const a = new A; A* aa = const_cast(a); aa->m_a = 2;
当然也可以用于添加 const
reinterpret_cast
格式为 reinterpret_cast
(expression) 非常激进的指针类型转换,在编译期完成,可以转换任何类型的指针,所以极不安全。非极端情况不要使用。
参考资料
C++笔记 · C++类型转换 —— 默默默默默
C++对象模型之RTTI的实现原理 —— linux
- 进行下行转换,即把父类对象的指针/引用转换成子类指针/引用,这种转换是不安全的,因为没有运行时检查,所以需要在编写程序时确认安全性
- 进行上行转换,即把子类对象的指针/引用转换为父类指针/引用,这种转换是安全的
还没有评论,来说两句吧...