第2.13章: 类型转换

English

隐式转换与类型提升

编译器在求值表达式时会自动把对象转换为共同的类型。 这被称为隐式转换或类型提升。


#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 风格转换。它们掩盖意图,并可能悄悄执行不安全的转换。 请始终优先使用现代的转换运算符。

要点