简介
C++ 类型转换是开发者必须掌握的重要技能之一, 无论是处理隐式转换还是显式转换, 理解其背后的机制与用法至关重要. 本篇博客旨在从基础到高级全面解析 C++ 的类型转换, 包括实际开发中的应用场景和性能分析.
自动转换
隐式类型转换
编译器可以在无需明确指示的情况下, 将一种类型的值自动转换为另一种兼容类型. 例如:
struct Struct {
Struct(float f) : m_(f) {}
float m_ = 0;
};
float f = -3.1415926;
double d = f;
int i = f;
size_t s = f;
char c = f;
Struct st = f;
赋值语句 | 值 |
---|---|
float f = -3.14; | -3.14159 |
double d = f; | -3.14159 |
int i = f; | -3 |
size_t s = f; | 18446744073709551613 |
char c = f; | 乱码 |
Struct st = f; | {.m_ = -3.14159} |
算术类型转换
void drawLine(uint8_t start, uint8_t end);
uint8_t x = 10;
uint8_t width = 50;
// 此处等价于: drawLine(x, static_cast<unsigned char>(static_cast<int>(x) + static_cast<int>(width)));
drawLine(x, x+width);
符号转换
void print(const std::vector<int>& vec) {
// 此处等价于: static_cast<unsigned long>(i) < vec.size()
for (int i = 0; i < vec.size(); i++) {
std::cout << i << ",";
}
}
用户转换运算符
class Struct {
public:
Struct(float f) : m_(f) {}
// 重载了转为int类型的操作符
operator int() const { return m_; }
private:
float m_ = 0;
};
int main() {
Struct si(1);
int i = si;
}
显示类型转换
C 风格类型转换
(type)var;
- 使用
var
创建<type>
的临时变量 <type>
可以是任何带有限定符的有效类型- 通过更改变量中位的含义来覆盖类型系统
- 在某些情况下无法编译(稍后详细介绍)
- 支持在
constexpr
上下文中使用(稍后详细介绍) - 可能导致未定义的行为
- 参与运算符优先级(级别 3)
struct A {};
struct B {};
int main() {
float f = 7.406f;
int i = (int)f; // int i = static_cast<int>(f);
A* pa = (A*)&f; // A* pa = reinterpret_cast<A*>(&f);
B* pb = (B*)pa; // B* pb = reinterpret_cast<B*>(pa);
double d = *(double*)(pb); // double d = *reinterpret_cast<double*>((pb));
return 0;
}
C 风格和函数式符号转换的问题
- 单一符号, 多重含义
- 容易出错
- 无法
grep
- 使 C 和 C++ 语法复杂化
C++ 强制转换的目标
- 不同的符号或不同的任务
- 易于识别和搜索
- 执行 C 强制转换可以执行的所有操作
- 消除意外错误
- 使强制转换不那么诱人
C++有如下几种类型转换的关键词:
static_cast
const_cast
dynamic_cast
reinterpret_cast
static_cast
T1 var;
T2 var2 = static_cast<T>(var)
- 从
var
类型创建临时变量 - 尝试通过隐式和用户定义的转换或构造找到从
T1
到T2
的路径. 无法删除const
限定.
使用场景:
-
阐明隐式转换
int i = 1; double d = static_cast<double>(i);
-
指示有意截断
int a = 1234; uint8_t u8 = static_cast<uint8_t>(a);
-
在基类和派生类之间进行强制转换
struct Base {}; struct Derived : public Base {}; Derived derived; Base& rb = derived; Derived& rd = static_cast<Derived&>(rb);
-
在
void*
和T*
之间进行强制转换struct MyStruct {}; void callback(void* handle) { auto p = static_cast<MyStruct*>(handle); //... }
多重转换
#include <cstdio>
struct A {
explicit A(int) { puts("A"); }
};
struct E {
operator int() {
puts("B::operator int");
return 0;
}
};
int main() {
E e;
A a = static_cast<A>(e);
return 0;
}
A
有一个接受单个int
的构造函数E
有一个用户定义的到int
的转换- 所以从
e
到a
的路径为:e
->int
->a
static_cast 与继承
#include <iostream>
struct B1 {
virtual ~B1() = default;
int i;
};
struct B2 {
virtual ~B2() = default;
int j;
};
struct Derived : public B1, public B2 {
int k;
};
void Compare(void* p1, void* p2) {
if (p1 == p2) {
std::cout << "Same.\n";
} else {
std::cout << "Different.\n";
}
}
int main() {
Derived d;
// pd 指向派生类
Derived* pd = &d;
// pb1 是指向基类B1的指针
B1* pb1 = static_cast<B1*>(&d);
Compare(pd, pb1); // Same.
// pb2 是指向基类B1的指针
B2* pb2 = static_cast<B2*>(&d);
Compare(pd, pb2); // Different.
void* derived_plus_offset = (char*)pd + sizeof(B1);
Compare(derived_plus_offset, pb2); // Same.
return 0;
}
为什么会出现这样的情况? 因为Derived
的布局为:
+---------+ <--- pd and pb1
| B1 |
+---------+ <--- pb2
| B2 |
+---------+
| Derived |
+---------+
...
static_cast 并非绝对正确
static_cast
无法防止向下转型为不相关的类型
#include <iostream>
#include <type_traits>
struct Base {
virtual void f() { std::cout << "base\n"; }
virtual ~Base() = default;
};
struct Derived : public Base {
void f() override { std::cout << "Derived\n"; }
};
struct Other : public Base {
void f() override { std::cout << "Other\n"; }
};
int main() {
Derived d;
Base& b = d; // OK
d.f(); // Derived
b.f(); // Derived
Other& a = static_cast<Other&>(b); // 危险, 转换到了其他类型
a.f(); // Derived
static_assert(std::is_same<decltype(a), Other&>::value, "not the same");
return 0;
}
const_cast
- 从变量中删除或添加
const
或volatile
限定符, 不能更改类型 - 不会更改原始变量的 CV 限定符
#include <iostream>
void use_pointer(int* p) { std::cout << "*p = " << *p << std::endl; }
void modify_pointer(int* p) {
*p = 42;
std::cout << "\tmodify_pointer *p <- 42\n"
<< "\tmodify_pointer *p = " << *p << std::endl;
}
int main() {
const int i = 7;
use_pointer(const_cast<int*>(&i));
modify_pointer(const_cast<int*>(&i));
std::cout << "i = " << i << std::endl; // i = 7
int j = 4;
const int* cj = &j;
modify_pointer(const_cast<int*>(cj));
std::cout << "i = " << i << std::endl; // i = 7
return 0;
}
输出
*p = 7
modify_pointer *p <- 42
modify_pointer *p = 42
i = 7
modify_pointer *p <- 42
modify_pointer *p = 42
i = 7
可以看到虽然在函数modify_pointer
里面指针指向的值发生了变化, 但是在外面的值却不受影响.
const_cast example: member overload
#include <stddef.h>
class my_array {
public:
char& operator[](size_t offset) {
// 此处调用const版本的实现, 避免重写一遍逻辑.
return const_cast<char&>(const_cast<const my_array&>(*this)[offset]);
}
const char& operator[](size_t offset) const { return buffer[offset]; }
private:
char buffer[10];
};
int main() {
const my_array a{};
const auto& c = a[4];
my_array mod_a;
mod_a[4] = 7;
return 0;
}
用于防止成员函数的代码重复.
运行时类型信息 (RTTI)
- 为实现定义的结构中的每个多态类型存储额外信息
- 允许在运行时查询类型信息
- 可以禁用以节省空间(gcc/clang:
–fno-rtti
, msvc:/GR-
)
dynamic_cast
- 查看 To 是否与 From 位于同一公共继承树中
- 只能是引用或指针
- 不能删除 CV
- From 必须是多态的
- 需要 RTTI
- 如果类型不相关, 则对指针返回 nullptr, 对引用抛出
std::bad_cast
#include <cstdio>
#include <vector>
struct A {
virtual ~A() = default;
};
struct B : public A {};
struct C : public A {};
int main() {
C c;
B b;
std::vector<A*> a_list = {&c, &b};
for (size_t i = 0; i < a_list.size(); ++i) {
A* pa = a_list[i];
if (dynamic_cast<B*>(pa)) {
printf("a_list[%lu] was a B\r\n", i);
}
if (dynamic_cast<C*>(pa)) {
printf("a_list[%lu] was a C\r\n", i);
}
}
return 0;
}
dynamic_cast 用例: UI 框架
struct Widget {};
struct Label : public Widget {};
struct Button : public Widget { void DoClick(); };
dynamic_cast
can be expensive
from gcc’s rtti.c
reinterpret_cast
#include <cstdint>
struct A {};
struct B {
int i;
int j;
};
int main() {
int i = 0;
int* pi = &i;
uintptr_t uipt = reinterpret_cast<uintptr_t>(pi);
float& f = reinterpret_cast<float&>(i);
A a;
B* pb = reinterpret_cast<B*>(&a);
char buff[10];
B* b_buff = reinterpret_cast<B*>(buff);
return 0;
}
- 可以将任何指针或引用类型更改为任何其他指针或引用类型
- 也称为类型双关
- 不能在 constexpr 上下文中使用
- 不能删除 CV 限定
- 不确保 To 和 From 的大小相同
- 适用于内存映射功能
reinterpret_cast 访问私有继承的基类
struct B {
void m() { puts("private to D"); }
};
struct D : private B {};
int main() {
D d;
B& b = reinterpret_cast<B&>(d);
b.m();
return 0;
}
Type Aliasing
当两种类型的内存布局兼容时, 将一种类型的内存当作另一种类型的内存来使用的行为.
compatible types
struct Point {
int x;
int y;
};
struct Location {
int x;
int y;
};
Point p{1, 2};
auto* loc = reinterpret_cast<Location*>(&p);
incompatible types
float f = 1.0f;
int* i = reinterpret_cast<int*>(&f);
C 风格类型转换在 C++ 中是如何实际执行的
对与一个类型转换
T conv = (T)val;
C++会依次尝试:
T conv = const_cast<T>(val);
T conv = static_cast<T>(val);
T conv = const_cast<T>(static_cast<const T>(val));
T conv = reinterpret_cast<T>(val);
T conv = const_cast<T>(reinterpret_cast<const T>(val));
如果找到匹配则会选择并执行编译, 否则会报错.
总结
C++ 提供了更安全, 更明确的类型转换工具, 开发者应根据场景选择合适的转换方式. 通过熟练掌握这些工具, 您可以编写更健壮, 更易维护的代码. 希望本博客能帮助您更深入地理解 C++ 类型转换的精髓!
参考资源
- Back to Basics: Casting - Brian Ruth - CppCon 2021