本文主要分析C++的4种显式类型转换
文章目录
我们使用显示类型转换,就是在告诉编译器要怎么解释这块内存。
在早期C/C++中,显式的类型转换有如下两种形式
cpp
type(expr); // 函数形式的显式类型转换
(type)expr; // C语言风格
C++的新式显式类型转换
cpp
cast-name<type>(expression);
cast-name
有static_cast
、dynamic_cast
、const_cast
和reinterpret_cast
四种,表示转换的方式type
表示转换的目标类型expression
是被转换的值
下面详细讲解下这四种显式类型转换
static_cast
格式为 static_cast<type>(expression)
任何在编写程序时能够明确的类型转换都可以使用 static_cast
:
基本数据类型转换
基本数据类型转换,比如将int
转换为double
,反之亦然,需要编写程序时确认安全性
将 `int` 转换为 `double` 编译器可能的操作如下(VS2022 x64),比如有如下代码
cpp
int x = 0;
double y = static_cast<double>(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
cpp
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;
};
- 进行上行转换,即把子类对象的指针/引用转换为父类指针/引用,这种转换是安全的
cpp
B b;
static_cast<A>(b).foo();
B* bptr = new B;
static_cast<A*>(bptr)->foo();
输出如下
- 进行下行转换,即把父类对象的指针/引用转换成子类指针/引用,这种转换是不安全的,因为没有运行时检查,所以需要在编写程序时确认安全性
cpp
A* a = new A;
std::cout << static_cast<B*>(a)->m_b << std::endl; // 不安全的,访问了申请外的内存
输出如下
void*
把 void*
转换为目标类型的指针(这是极其不安全的),类似于上面父类指针转子类指针,可能会访问申请外的内存
cpp
void* a = new A;
std::cout << static_cast<B*>(a)->m_b << std::endl; // 不安全的,访问了申请外的内存
输出如下
dynamic_cast
格式为 dynamic_cast<type>(expression)
相比static_cast
,dynamic_cast
会在运行时检查类型转换是否合法,具有一定的安全性。由于运行时的检查,所以会额外消耗一些性能。需要注意的是 dynamic_cast 转换仅适用于指针或引用。
继承中的转换
- 上行转换
由子类指针/引用转换到父类指针/引用,与 static_cast
和隐式转换一样,都是安全的
cpp
B* b = new B;
A* a = dynamic_cast<A*>(b);
std::cout << a->m_a << std::endl;
可以看到下面的汇编代码中,只是把指针的值给到了 [a]
地址
- 下行转换
由父类指针/引用转换到子类指针/引用,使用 dynamic_cast
进行下行转换有个条件,就是其操作数必须含有多态类类型,简单理解,父类得有虚函数。
如果我们将之前 类A
和 类B
中的虚函数去掉,则会报错
如果和之前一样,类 A
中有虚函数 foo()
,那么执行 dynamic_cast
会发生什么
cpp
A* a = new A;
B* b = dynamic_cast<B*>(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<type>(expression)
const_cast
用于移除类型的 const
、volatile
和 _unaligned
属性
cpp
const A* const a = new A;
A* aa = const_cast<A*>(a);
aa->m_a = 2;
当然也可以用于添加 const
reinterpret_cast
格式为 reinterpret_cast<type>(expression)
非常激进的指针类型转换,在编译期完成,可以转换任何类型的指针,所以极不安全 。非极端情况不要使用。