09 - C++中的类型转换
C++中的类型转换
C++提供了5种特定的强制类型转换:const_cast()
,static_cast()
,reinterpret_cast()
,dynamic_cast()
和C++20新出的std::bit_cast()
。虽然我们还能用旧的C风格强制类型转换,但那容易出错,推荐使用C++提供的类型转换。
const_cast()
可以使用它 为变量添加或取消const
属性,这是5种类型转换种唯一可以消除const
属性的转换。
首先是取消const
属性的情况。自己的函数接收常量字符串,但第三方函数却不是,此时就要使用const_cast<char*>()
为变量取消const
属性:
void thirdPartyFunc(char* str);
void myFunc(const char* str)
{
thirdPartyFunc(const_cast<char*>(str));
}
然后是添加const
属性的情况。标准库提供了一个名为std::as_const()
的辅助方法,它在<utility>
中被定义,该方法接收一个引用参数,并返回它的const&
版本,相当于const_cast<const T&>(obj)
:
std::string str("C++");
const std::string& constStr(std::as_const(str));
static_cast()
可以使用它 执行语言直接支持的显式转换。例如在计算小数除法时,为了避免整数除法,常常使用显式转换:
int i = 3, j = 4;
double res = static_cast<double>(i) / j;
它的另一个用途就是 在继承层次结构中执行向下的强制转换,这些强制类型转换既可以使用指针也能使用引用,它们不使用对象:
class Base
{
public:
virtual ~Base() = default;
};
class Derived : public Base
{
public:
virtual ~Derived() = default;
};
int main()
{
Base* b = new Base();
Derived* d = new Derived();
// 对于从子类到父类的转换, 无需使用cast
b = d;
// 对于从父类到子类的转换, 需要使用cast
d = static_cast<Derived*>(b);
Base base;
Derived derived;
Base& br = derived;
Derived& der = static_cast<Derived&>(base);
}
PS:这种转换需要谨慎使用,注意的点在下方。
使用static_cast()
需要注意的是:
使用
static_cast()
的强制类型转换不会执行运行时类型检查。就拿刚刚的例子来看,如果要强行使用d,可能会导致潜在的故障,例如在对象边界之外进行内存覆盖(假设Derived类比Base类内容多,那d就要操控Base对象之外的内存)。执行运行时类型检查的转换是待会要说的
dynamic_cast()
。不能进行不相干类型间的转换,例如不能将指针转换为整数、不能将一种类型的指针转换为另一种不相干类型的指针等。
dynamic_cast()
它提供了对继承层次结构中的强制转换的运行时类型检查,可以使用它强制转换指针/引用。dynamic_cast()
在运行时检查底层对象的运行时类型信息,如果强制转换无效,会返回空指针/抛出异常。
Base* b = new Base();
Derived* d = new Derived();
b = d;
d = dynamic_cast<Derived*>(b);
Base base;
Derived derived;
Base& br = derived;
try
{
Derived& der = static_cast<Derived&>(base);
// do sth...
}
catch (const std::bad_cast&)
{
std::cout << "Bad Cast!!\n";
}
由于运行时的类型信息存储在对象的 vtable 中,要使用dynamic_cast()
,类中必须至少具有一个虚方法,否则会编译错误。
reinterpret_cast()
这种转换更加底层,功能也更强大,相对的安全性也更低了。可以用它 执行C++类型规则中在技术上不允许的某些强制转换,因为它运行在不执行任何类型检查的情况下进行转换。
在程序员 明知 A类型变量的内存分布后,可以使用该转换将这片内存重新解释成是B类型变量的内存分布。例如,可将一种类型的引用强制转换为对另一种类型的引用、将void*
强制转换为某种类型的指针等。
class X {};
class Y {};
int main()
{
X x;
Y y;
X* p_x = &x;
Y* p_y = &y;
// 不相干类型指针间的转换
p_x = reinterpret_cast<X*>(p_y);
// 具体类型 -> void* 无需显式转换
void* p = p_x;
// void* -> 具体类型 需要显式转换
p_x = reinterpret_cast<X*>(p);
p_x = static_cast<X*>(p);
// 不相干类型引用间的转换
X& r_x = x;
Y& r_y = reinterpret_cast<Y&>(x);
}
使用reinterpret_cast()
也是存在一些限制的:
【待补充】
std::bit_cast()
C++20中引入了std::bit_cast()
,定义在<bit>
中。这是标准库中唯一的强制类型转换,其他强制转换是C++语言本身的一部分。它与reinterpret_cast()
类似,但它会创建一个指定目标类型的新对象,并按位从源对象复制到此新对象。它要求源对象和目标对象的大小相同,且二者均可复制。
例如:
float asFloat = 1.23f;
auto asUint = std::bit_cast<unsigned int>(asFloat);
std::cout << std::format("asUint = {}\nstd::bit<float>(asUint) = {}", asUint, std::bit_cast<float>(asUint));
输出为:
asUint = 1067282596
std::bit<float>(asUint) = 1.23
它通常用于逐字节读文件到内存,然后用它正确地解释从文件中读取的字节。
小结
综上,下边总结了在不同情况下应使用的强制类型转换:
场景 | 类型转换 |
---|---|
移除const 属性 | const_cast() |
语言支持的显式强制转换(int -> double等) | static_cast() |
用户定义的构造函数/转换函数支持的显式强制转换 | static_cast() |
类对象转换为另一个(无关的)类对象 | std::bit_cast() |
在同一继承层次结构中,类指针转换为另一个类指针 | dynamic_cast() |
在同一继承层次结构中,类引用转换为另一个类引用 | dynamic_cast() |
指向一种类型的指针转换为指向其他不相干类型的指针 | reinterpret_cast() |
一种类型的引用转换为其他不相干类型的引用 | reinterpret_cast() |
指向函数的指针转换为指向其他函数的指针 | reinterpret_cast() |
参考资料
- 飘零的落花 - 现代C++详解
- C++20高级编程