条款 15 - 只要有可能使用 constexpr, 就使用它
constexpr(Constant Expression,常量表达式)是 C++11 引入的一个至关重要的关键字,它允许我们将计算的执行时机从运行时 (Runtime) 提前到编译时 (Compile Time)。
这不仅仅是一项微小的优化,它从根本上改变了 C++ 的编程范式,使得在编译阶段进行复杂的计算和逻辑判断成为可能,从而提高了程序的运行效率并增强了编译期的检查能力。
constexpr 对象/变量
当 constexpr 应用于对象时,它实际上是 const 属性的加强版。
const 对象:一个被声明为 const 的对象,其值在初始化后不能被修改。但是,它的初始值不一定在编译期就已知。它可以由一个运行时才计算出的值来初始化。
1 | int sz; |
constexpr 对象:该对象不仅是 const 的,而且其值必须在编译期就已知(准确地说是“翻译期间”可知)。并且所有 constexpr 对象都是 const 对象,但并非所有 const 对象都是 constexpr 对象。正因如此, 它强制要求该变量必须在编译时被一个“常量表达式”初始化
由于其值在编译期已知,constexpr 对象拥有特权,它们可以被用在 C++ 要求“整型常量表达式”的任何语境中,例如指定数组的尺寸(C-style 数组,非 std::vector)int arr[N] (这里的 N 必须是编译期常量)、作为模板的非类型参数(Non-Type Template Arguments) std::array<int, N> (这里的 N 必须是编译期常量)、枚举量的初始化值等。
1 | constexpr auto arraySize2 = 10; // 没问题,10 是编译期常量 |
constexpr 函数
当 constexpr 应用于函数时,其含义变得更加灵活: 首先, 一个 constexpr 函数不一定返回编译期已知的结果。
- 如果一个 constexpr 函数被调用时,传入的实参都是编译期常量,那么该函数的产出结果也将是一个编译期常量。
- 如果它被调用时,传入的参数中至少有一个是运行时才已知的值,那么该函数将会在运行时被执行,其行为和结果都与普通函数无异。
这是 constexpr 函数最强大的特性:只需编写一次函数,它就可以同时服务于编译期和运行时两种场景。
1 | // 一个 constexpr 阶乘函数 |
我们只编写了一个 factorial 函数,但它既能用于需要编译期常量的模板参数(场景1),也能用于处理运行时的用户输入(场景2)。这就是 constexpr 函数的核心价值。
需要注意的是, 上面那个 factorial 示例就是 C++11 风格的。在 C++11 中,constexpr 函数几乎是“函数式”的。它内部只能包含一个单独的 return 语句, 不允许有局部变量、循环,只允许使用(极其复杂的)递归和三元运算符(?:)来实现逻辑。而C++14后的constexpr 函数基本与正常函数无异