C++ / a working model

54 / 80   ·   C++20   ·   约 9 分钟

constexpr、consteval 与 constinit 各管什么

先记住这句话

constexpr 表达常量求值的能力与变量约束,consteval 要求立即调用满足常量表达式条件,constinit 约束静态或线程存储期变量的初始化。三者并不等价,尤其 constinit 不会使变量只读。

本篇内容
  1. constexpr:可在常量上下文使用
  2. consteval:调用必须满足立即求值规则
  3. constinit:管初始化,不管后续修改
  4. 运行示例
  5. 动手练习

constexpr:可在常量上下文使用

C++11 的 constexpr 变量要求合适的常量初始化,并使对象为 const;constexpr 函数则提供进入常量求值的能力,但普通调用不一定必须在翻译期间求值。把结果放进 constexpr 变量或 static_assert,才明确建立本次调用的常量表达式要求。

从 C++14 起函数体规则逐步放宽,循环和局部变量等写法得以用于常量计算,但仍需按目标标准检查限制。constexpr 不是“永远更快”的注解,编译器本来也可以优化普通表达式;它首先提供语言层面的可验证契约,而不是性能测量结论。

consteval:调用必须满足立即求值规则

C++20 的 consteval 声明立即函数,普通潜在求值调用必须满足常量表达式要求,不能把只有运行期才知道的输入直接传入。它适合编译期校验、固定表生成等确实不允许退回运行期的接口,而不是所有小函数的默认修饰。

示例的 square 同时支持常量与运行期语境,而 fixed_limit 明确是立即函数。两者都不会因为词法上出现在 main 外就自动获得正确结果;决定性因素是函数声明、实参与调用上下文。对外提供工具时,应先决定是否真的需要禁止运行期调用。

constinit:管初始化,不管后续修改

C++20 的 constinit 用于静态或线程存储期变量,要求不存在动态初始化。它可把意外引入的运行期初始化变成编译错误,帮助控制跨翻译单元的初始化依赖,却不会让之后的读写自动线程安全,也不会让变量自动变成 const。

示例让全局 counter 以常量方式初始化,然后在 main 中递增,证明 constinit 与只读是两回事。若既要常量值又不允许修改,constexpr 更合适;若需要可变全局状态而只想锁定初始化阶段,可考虑 constinit,但优先评估是否能减少全局共享状态。

容易答错的地方

  • constexpr 函数遇到运行期实参仍可正常执行;不能把函数带 constexpr 当作每次调用都完成了编译期计算的证据。
  • constinit 不是 const,不约束后续写入,也不提供同步;多线程修改仍要使用适当的锁或原子对象。

运行一个例子

最低标准 C++20 · 完整程序 · 下载 .cpp

#include <cassert>
#include <iostream>

constexpr int square(int x) { return x * x; }
consteval int fixed_limit() { return square(4); }
constinit int counter = square(2);

int main() {
    constexpr int limit = fixed_limit();
    static_assert(limit == 16);
    int input = 3;
    const int result = square(input);
    ++counter;
    assert(result == 9 && counter == 5);
    std::cout << limit << ' ' << result << ' ' << counter << '\n';
}

在本地编译

g++ -std=c++20 -Wall -Wextra -Wpedantic -pthread modern-constexpr.cpp -o example && ./example

预期结果

16 9 5

CHECK YOUR UNDERSTANDING

合上答案,试着解释。

为什么 constinit int local = 4 不能直接作为 main 中普通自动变量,而 static constinit int local = 4 可以?

查看参考答案

constinit 约束静态或线程存储期初始化,普通块内自动变量不属于该范围。增加 static 后变量获得静态存储期,并以常量 4 初始化,满足要求。它仍能在之后赋值;若只是需要块内不可变常量,应直接写 constexpr int local = 4。

继续查证

标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。

回到目录