C++ static_cast,reinterpret_cast 和 const_cast
C++11 开始,弃用了 C 风格的显示类型转换,额外提供了 3 个模板来完成不同的类型转换,便于调试。
static_cast
static_cast 常见用途有两点,第一点是显示转换所有隐式转换,第二点是在相关的类(类型)之间进行转换。
具体来说,static_cast 支持以下转换:
初始化转换
int n = static_cast<int>(3.14);
std::vector<int> v = static_cast<std::vector<int>>(10);
static_cast 实际上可能会有潜在的临时变量生成,但是 C++17 开始强制要求返回值优化(ROV),即将返回值直接写入接收的变量,而不产生额外的复制。
其中包含返回左值和返回纯右值,注意不要在返回值使用 std::move,因为 std::move 返回将亡值,不利于编译器进行优化;并且不能返回右值引用,因为会导致悬垂引用。
派生类转换为基类的引用
这个操作是安全的,也可以反过来,但是会变为不安全。
struct B {
int m = 0;
void hello() const {
std::cout << "Hello world,这里是 B!" << std::endl;
}
};
struct D : B {
void hello() const {
std::cout << "Hello world,这里是 D!" << std::endl;
}
};
int main()
{
D d;
B& br = d; // 通过隐式转换向上转型
br.hello();
D& another_d = static_cast<D&>(br); // 向下转型
another_d.hello();
B& another_b = static_cast<D&>(another_d);
another_b.hello();
}
将左值转换为右值引用
这实际上就是 std::move 的实现(通过模板)。
int main()
{
std::vector<int> v1 = static_cast<std::vector<int>>(10);
std::vector<int> v2 = static_cast<std::vector<int> &&>(v);
std::cout << "移动后,v.size() = " << v.size() << std::endl;
}
移动后 v1 的大小会变为 0,换句话说 v1 的所有权交给了 v2。
弃值表达式
即将函数的返回值丢弃,但是显式的用 static_cast 表示,这是 C++ 中唯一能传递 void 的地方。
static_cast<void>(v2.size());
在 void* 之间进行转换(不安全)
int n = 3.14;
void* nv = &n;
int* ni = static_cast<int*>(nv);
nv = static_cast<void*>(ni);
std::cout << "*ni = " << *ni << std::endl;
将数组显式退化到指针,并且可以伴随进行相关类型的转换
D a[10];
B* dp = static_cast<B*>(a);
限定作用域枚举转换为其他类型(必须)和非限定枚举的隐式转换的显式表示
enum class E { ONE = 1, TWO, THREE };
enum EU { ONE = 1, TWO, THREE };
E e = E::ONE;
int one = static_cast<int>(e);
std::cout << one << std::endl;
E e2 = static_cast<E>(one);
EU eu = static_cast<EU>(e2);
类的成员函数的指针转换
int main()
{
D d;
B& br = d;
int D::*pm = &D::m;
std::cout << br.*static_cast<int B::*>(pm) << std::endl;
int B::*wm = &B::m;
std::cout << d.*static_cast<int D::*>(wm) << std::endl;
}
const_cast
const_cast 的目的是转换 const 和 volatile,可用于去除或者添加(通常是去除)
const_cast 使得到非 const 类型的引用或指针能够实际指代 const 对象,或到非 volatile 类型的引用或指针能够实际指代 volatile 对象。通过转换后的非 const 访问路径修改 const 对象和通过非 volatile 泛左值涉指 volatile 对象是未定义行为。
const_cast 是让编译器进行一个“宽松的”审查,从而忽略 const 标识,并不会带来额外性能上的开销。
struct type {
int i;
type(): i(3) {}
void f(int v) const {
// this->i = v; // 编译错误:this 是指向 const 的指针
const_cast<type*>(this)->i = v; // 只要该对象 (i)不是 const 就 OK
}
};
int main()
{
int i = 3; // 不声明 i 为 const
const int& rci = i;
const_cast<int&>(rci) = 4; // OK:修改 i
std::cout << "i = " << i << '\n';
type t; // 假如这是 const type t,则 t.f(4) 会是未定义行为
t.f(4);
std::cout << "type::i = " << t.i << '\n';
const int j = 3; // 声明 j 为 const
[[maybe_unused]]
int* pj = const_cast<int*>(&j);
// *pj = 4; // 未定义行为
[[maybe_unused]]
void (type::* pmf)(int) const = &type::f; // 指向成员函数的指针
// const_cast<void(type::*)(int)>(pmf); // 编译错误:const_cast 不能用于成员函数指针
}
reinterpret_cast
reinterpret_cast 大部分作用是在二进制的基础上对数据进行重新解释,即对类型无关的指针进行转换,但是不能代替 const_cast。
reinterpret_cast 和 const_cast 一样没有副作用,同时 reinterpret_cast 有些时候相当于两步 static_cast(即转换为 void* 再从 void* 转换为其他类型的指针)。
reinterpret_cast 的安全性完全由程序员负责,很有可能发生不安全的访问(例如将 uint32_t 解释为 uint64_t,再去获得 uint64_t 的数据,可能会发生访问越界以及段错误)。
还可以使用 reinterpret_cast 进行反退化操作:
int main()
{
int(*n2)[3][2] = reinterpret_cast<int(*)[3][2]>(new int[3][2]);
(*n2)[0][0] = 1;
int n3[3][2];
auto *n4 = &n3;
std::cout << typeid(n2).name() << std::endl;
std::cout << typeid(*n2).name() << std::endl;
std::cout << typeid(n4).name() << std::endl;
std::cout << typeid(*n4).name() << std::endl;
delete n2;
n2 = n4;
}