第2.13章: 类型转换
隐式转换与类型提升
编译器在求值表达式时会自动把对象转换为共同的类型。 这被称为隐式转换或类型提升。
#include <cstddef>
#include <cstdint>
::std::uint8_t a{10u};
::std::uint8_t b{20u};
auto sum{a + b}; // both promoted to int before addition
// sum has type int, not ::std::uint8_t
小的整数类型(char、unsigned char、short、unsigned short)在做算术运算时会被提升为 int(或 unsigned int)。 混合类型时,编译器会提升到共同类型,以安全地完成计算。
#include <cstddef>
double pi{3.14};
::std::size_t n{2zu};
auto result{pi * n}; // n promoted to double, result is double
显式转换概览
当你需要特定的转换时,请使用显式转换。现代 C++ 为不同的意图提供了不同的运算符:
static_cast、const_cast、reinterpret_cast 以及 ::std::bit_cast(C++20)。
⚠️ 但请记住:如果能够完全避免转换,那通常是最好的选择。 请设计你的代码,让类型自然匹配。
static_cast
对于有良好定义、编译期可检查的转换(例如数值类型之间的转换),请使用 static_cast。
#include <cstddef>
double pi{3.14159};
::std::size_t n{static_cast<::std::size_t>(pi)}; // converts double to size_t
const_cast
使用 const_cast 来添加或移除 const 限定符。
#include <cstddef>
::std::size_t const x{42zu};
auto& y{const_cast<::std::size_t&>(x)}; // removes const
⚠️ 如果 x 确实是 const 的,那么通过 y 写入就是未定义行为。
reinterpret_cast
reinterpret_cast 重新解释对象的位模式。
⚠️ 它通常是错的,应当避免使用。
唯一常见的例外是转换到 char* 或 unsigned char* 以检查原始字节。
不要使用 signed char* 来做类型双关。
即便使用 char* 或 unsigned char*,你也必须考虑严格别名规则。
参见:
什么是严格别名规则,我们为什么要关心它?
。
我们会在将来的指针章节中进一步讨论。
#include <cstdint>
::std::uint32_t u{0xDEADBEEFu};
auto p{reinterpret_cast<unsigned char const*>(&u)}; // raw byte view
如果你想把一种类型当作另一种类型(例如把 float64_t 当作 uint64_t),
你不应使用 reinterpret_cast,
而应使用 ::std::bit_cast。
::std::bit_cast
当源类型与目标类型大小相同且都是可平凡复制的时候,::std::bit_cast 可以安全地重新解释位。
#include <bit>
#include <cstdint>
double d{3.14};
::std::uint64_t bits{::std::bit_cast<::std::uint64_t>(d)}; // safely copies bit pattern
dynamic_cast
dynamic_cast 与多态配合使用,可在基类与派生类之间安全转换。
在实践中它很慢、很复杂,在性能关键的代码中常被禁用。
Herb Sutter 已为将来提出了 ::std::down_cast,
当它不跨越虚继承链或 DLL 边界时,开销会小得多。
目前:请避免 dynamic_cast。优先采用不需要它的设计。
你完全可以跳过本节——我们把它列在这里,只是为了说明存在 dynamic_cast 这么个东西。
等将来讲到指针与多态时,我们会认真讨论它。
下面是一个非常简单的例子,只为展示语法:
struct Base { virtual ~Base() = default; };
struct Derived : Base { };
Derived d;
Base* b = ::std::addressof(d);
Derived* pd = dynamic_cast<Derived*>(b); // safe downcast
if(pd) {
print("dynamic_cast succeeded\n");
}
这能成立,是因为 b 实际指向一个 Derived。
如果 b 指向别的子类,dynamic_cast 会返回 nullptr。
C 风格转换
C 风格转换看起来像 (type)expression。
它之所以危险,是因为它尝试得太多:
编译器会先尝试 const_cast,再尝试 static_cast,必要时最后尝试 reinterpret_cast。
这让人无法看清实际发生的是哪一种转换。
#include <cstddef>
double pi{3.14159};
::std::size_t n{(::std::size_t)pi}; // C-style cast
⚠️ 避免 C 风格转换。它们掩盖意图,并可能悄悄执行不安全的转换。 请始终优先使用现代的转换运算符。
要点
- 隐式转换会把小整数提升为 int,并在混合表达式中找出共同类型。
- 对清晰、编译期可检查的转换,使用
static_cast。 - 只有当中层对象确实非 const 时,才用
const_cast改变限定符。 reinterpret_cast通常是错的;只在检查字节时考虑char*/unsigned char*,并留意严格别名规则。- 对安全、显式的位重新解释,使用
::std::bit_cast。 dynamic_cast确实存在,但很慢且常被禁用。上面的语法仅供了解。- 避免 C 风格转换;它会尝试过多转换(const_cast → static_cast → reinterpret_cast)并掩盖意图。
- 如果能完全避免转换,那是最好的——请设计代码,让类型自然匹配。