从 C++ 基础到掌握 — 第 17 集
使用 constexpr 和 consteval 进行编译期计算
本博客文章以教学为目的编写。 内容可能存在错误,请结合目标编译器和标准库文档进行确认。
C++ 能在程序启动前完成部分计算。constexpr、consteval 和 constinit 分别表达可常量求值、必须在编译期调用,以及静态初始化。
constexpr 可在两种环境执行
constexpr int Square(int value)
{
return value * value;
}
constexpr int tableSize = Square(8); // 编译期
int input = ReadInput();
int result = Square(input); // 运行期
constexpr 函数可以参与常量求值,但不是每次调用都必须发生在编译期。参数为常量且结果用于需要 constant expression 的位置时,编译器必须先求出结果。
consteval 强制编译期调用
consteval unsigned int HashLiteral(std::string_view text)
{
unsigned int hash = 2166136261u;
for (char ch : text)
hash = (hash ^ static_cast<unsigned char>(ch)) * 16777619u;
return hash;
}
constexpr auto playerId = HashLiteral("Player");
consteval 的每次调用都必须是 constant expression,适合构建时就固定的资源键或协议标签。
哈希仍可能冲突。编译期计算不保证唯一性,因此仍需要冲突策略和原始键验证。
用 if constexpr 移除无效分支
template <typename T>
void Serialize(const T& value, Writer& writer)
{
if constexpr (std::integral<T>)
writer.WriteInteger(value);
else
value.Serialize(writer);
}
与普通 if 不同,未选择的 if constexpr 分支会从该模板实例中丢弃。另一分支中不适用于当前类型的操作不必成立。
constinit 检查静态初始化
constinit int globalLimit = 64;
constinit 验证 static storage duration 对象能否静态初始化,但不会让对象不可修改;不可修改是 const 的职责。
编译期计算能减少运行期初始化并提前暴露错误,但会增加构建时间、诊断复杂度,有时也会增大二进制。只在规则真正固定且提前验证值得成本时使用。