章节目录

Constexpr

本节阅读量:

constexpr关键字

当使用const关键字声明const变量时,编译器将隐式跟踪它是运行时常量还是编译时常量。在大多数情况下,运行时常量还是编译时常量对于优化以外的任何事情都无关紧要,但在一些情况下,C++需要常量表达式(我们将在稍后介绍这些主题时介绍这些情况)。并且在常量表达式中只能使用编译时常量。

由于编译时常量还允许更好的优化(并且没有什么缺点),因此我们通常希望尽可能使用编译时常量。

当使用const时,变量是编译时常量或运行时常量,这取决于初始值是否是编译时常量表达式。在某些情况下,很难区分。

例如:

1
2
3
4
int x { 5 };       // 非 const
const int y { x }; // 运行时常量 (因为使用非const变量初始化)
const int z { 5 }; // 编译时常量
const int w { getValue() }; // 不是很容易判断

在上面的示例中,w可以是运行时常量,也可以是编译时常量,具体取决于getValue() 的定义方式。一点也不清楚!

幸运的是,我们可以获得编译器的帮助,以确保在需要的地方获得编译时常量。为此,我们在变量的声明中使用constexpr关键字而不是const。constexpr(“常量表达式”的缩写)变量只能是编译时常量。如果constexpr变量的初始化值不是常量表达式,编译器将报错。

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>

int five()
{
    return 5;
}

int main()
{
    constexpr double gravity { 9.8 }; // ok: 9.8 是常量表达式
    constexpr int sum { 4 + 5 };      // ok: 4 + 5 是常量表达式
    constexpr int something { sum };  // ok: sum 是常量表达式

    std::cout << "Enter your age: ";
    int age{};
    std::cin >> age;

    constexpr int myAge { age };      // 编译报错: age 不是常量表达式
    constexpr int f { five() };       // 编译报错: five() 返回值不是常量表达式

    return 0;
}

const和constexpr函数参数

普通函数调用在运行时求值。这意味着函数参数即使是编译时常量,也会被视为运行时常量。

由于constexpr对象必须用编译时常量(而不是运行时常量)初始化,因此不能将函数参数声明为constexpr。


何时实际计算常量表达式?

编译器在需要将常量表达式结果作为常量时(如编译时常量的初始值)计算常量表达式:

1
2
constexpr int x { 3 + 4 }; // 3 + 4 永远在编译时计算
const int x { 3 + 4 };     // 3 + 4 永远在编译时计算

在不需要结果是常量表达式的上下文中,编译器可以选择是在编译时还是在运行时计算常量表达式。

1
int x { 3 + 4 }; // 3 + 4 可能在编译时,也可能在运行时计算

在上面的变量定义中,x不是constexpr变量,并且在编译时不需要知道初始化值。因此,编译器可以自由选择是在编译时还是在运行时计算3+4。

尽管它不是严格要求的,但现代编译器通常会在编译时计算常量表达式,因为这样做性能更高。


常量折叠(Constant folding)

考虑以下示例:

1
2
3
4
5
6
7
8
9
#include <iostream>

int main()
{
	constexpr int x { 3 + 4 }; // 3 + 4 是常量表达式
	std::cout << x << '\n';    // 这是运行时表达式

	return 0;
}

3+4是一个常量表达式,同时编译器将在编译时计算3+4,并将其替换为值7。由于x是编译时常数,编译器可能会完全优化上述程序中的x,将std::cout « x « ‘\n’ 替换为 std::cout « 7 «’\n’。输出表达式将在运行时执行。

然而,由于x仅使用一次,因此我们更有可能首先这样编写程序:

1
2
3
4
5
6
7
#include <iostream>

int main()
{
	std::cout << 3 + 4 << '\n'; // 这是运行时表达式
	return 0;
}

由于表达式 std::cout « 3 + 4 « ‘\n’ 不是常量表达式,因此有理由怀疑常量子表达式3+4是否仍将在编译时优化。

答案通常是“是”。编译器早就能够优化常量子表达式,即使完整表达式是运行时表达式。这种优化过程称为“常量折叠”。

将变量设为constexpr,可以确保在常量子表达式中使用这些变量时,它们符合常量折叠的条件。


5.1 常量表达式、编译时常量和运行时常量

上一节

5.3 字面值常量

下一节