在 C++20 及以后版本,constexpr 逐步变得更加强大,几乎可以把任何在编译期可求值的表达式都用作常量。下面从概念、语法、典型用例以及性能影响四个方面,系统地梳理 constexpr 的使用。
1. constexpr 的基本含义
- 编译期常量:
constexpr修饰的变量、函数或对象,在编译阶段必须确定其值。编译器会在编译期间对其进行求值。 - 不可变性:
constexpr对象是常量,不能再被修改;其构造必须在编译期完成。 - 递归与尾递归:编译期求值支持递归调用,但有递归深度限制(编译器可配置)。
2. 语法细节
constexpr int factorial(int n) { // constexpr 函数
return n <= 1 ? 1 : n * factorial(n - 1);
}
constexpr int table[5] = {0, 1, 2, 3, 4}; // constexpr 数组
constexpr std::array<int, 4> arr = {1,2,3,4};
constexpr struct Point { // constexpr 结构体
int x, y;
constexpr Point(int a, int b) : x(a), y(b) {}
} p{5,10};
constexpr int factorial5 = factorial(5); // 直接在编译期计算
2.1 constexpr 函数的约束
- 参数必须是常量表达式,或者在调用时提供常量。
- 体内只能有可在编译期求值的语句:没有
goto、try、throw等异常机制,不能有非constexpr对象的引用。 - 对于类成员函数,声明时必须使用
constexpr,定义时可以单独写constexpr或者在声明中省略。
2.2 变长模板参数
template <size_t... N>
constexpr std::array<int, sizeof...(N)> make_array() {
return {{ N... }};
}
constexpr auto arr2 = make_array<1,2,3,4>();
3. 常见使用场景
| 场景 | 目的 | 代码示例 |
|---|---|---|
| 编译期数组生成 | 根据模板参数生成静态数组,避免运行时循环 | constexpr auto arr = make_array<0,1,2,3>(); |
| 静态断言 | 在编译期检查某些条件 | static_assert(sizeof(int) == 4, "int size not 4"); |
| 多态与类型擦除 | 用 constexpr 计算类类型的哈希或标识 |
constexpr uint64_t type_hash = fnv1a_hash(typeid(T).name()); |
| 嵌入式编程 | 减少运行时开销,提升执行速度 | constexpr uint16_t mask = 0xFFFF; |
4. 性能与实践
- 编译器优化:constexpr 让编译器在编译阶段完成计算,运行时不再需要执行,直接把常量值内联到代码中。
- 内存占用:constexpr 变量会被放到只读数据区,和普通
const并无区别。使用constexpr只在编译期执行,并不在运行时占用额外空间。 - 调试体验:在调试器中查看
constexpr变量值会直接显示其编译期值,便于验证。
5. 小结
constexpr是 C++ 语言中实现编译期计算的核心工具。通过constexpr函数、变量、结构体等,我们可以在编译阶段完成复杂计算,提升程序的性能与安全性。- 在使用时,需要关注编译器的递归深度限制、语法约束以及潜在的编译时间增长。通常在小型或中等规模项目中使用
constexpr可以带来显著收益。 - 通过结合
static_assert与constexpr,可以在编译期间捕捉错误,极大地提升代码质量。
实战建议:在需要高性能的数值计算或数据初始化时,优先尝试使用
constexpr;在需要验证类或函数接口时,配合static_assert。随着编译器的不断优化,constexpr的编译期开销将持续下降,成为 C++ 开发不可或缺的工具之一。