一文总结 C++ 常量表达式、constexpr 和 const
TLDR
- 修饰变量的时候,可以把 constexpr 对象当作加强版的 const 对象:const 对象表明值不会改变,但不一定能够在编译期取得结果;constexpr 对象不仅值不会改变,而且保证能够在编译期取得结果。如果一个 const 变量能够在编译期求值,将其改为 constexpr 能够让代码更清晰易读。
- constexpr 函数可以把运行期计算迁移至编译期,使得程序运行更快(但会增加编译时间)。但如果 constexpr 函数中存在无法在编译期求值的参数,则 constexpr 函数和普通一样在运行时求值,此时的返回值不是常量表达式。
1. 常量表达式和 constexpr
C++11 中引入了 constexpr
关键字。constexpr 是 const expression 的缩写,即常量表达式。
常量表达式是指值不会改变且编译期可以得到结果的表达式。
1.1 特点
- 值不会改变(这一点和普通 const 一样)
- 编译期就能得到结果!(普通 const 不一定保证)
1.2 使用场景
C++ 在一些场景下必须使用常量表达式,比如:
- 数组大小
- 整型模板实参(如
std::array<T, N>
的长度参数 N) - switch-case 中的 case 标签
- 枚举量的值
- 对齐规格
1.3 常见的常量表达式
- 字面值(如 42)
- 用常量表达式初始化的 const 对象
一个对象(或表达式)是否是常量表达式取决于类型和初始值,如:
int i1 = 42; // i1 不是常量表达式:初始值 42 是字面值,但 i1 不是 const 类型
const int i2 = i1; // i2 不是常量表达式:初始值 i1 不是常量表达式
const int i3 = 42; // i3 是常量表达式:用字面值 42 初始化的 const 对象
const int i4 = i3 + 1; // i4 是常量表达式:用常量表达式 i3 + 1 初始化的 const 对象
const int i5 = getValue(); // 如果 getValue() 是普通函数,则 i5 值要到运行时才能确定,则不是常量表达式
1.4 constexpr 变量
上面的例子可以看出,不能直接判断一个 const 对象是否是常量表达式:例如 i4 是否是常量表达式取决于 i3 是否是常量表达式,而 i4 又可能用来初始化其他常量表达式。在复杂的系统中,很难一眼看出某个 const 对象是否是常量表达式。
C++11 允许把变量声明为 constexpr 类型,此时编译器会保证 constexpr 变量是常量表达式(否则编译报错)。换句话说,只要看到 constexpr 类型的变量,则一定能够在编译期取得结果,可以用在需要常量表达式的场景。
int i1 = 42;
constexpr int i2 = i1; // constexpr 变量 'i2' 必须由常量表达式初始化。不允许在常量表达式中读取非 const 变量 'i1'
constexpr int i3 = 42; // i3 是常量表达式
constexpr int i4 = i3 + 1; // i4 是常量表达式
constexpr int i5 = getValue(); // 只有 getValue() 是 constexpr 函数时才可以,否则编译报错
1.5 constexpr 函数
constexpr 函数是指能用于常量表达式的函数。
需要强调的是,constexpr 函数既能用于要求常量表达式/编译期常量的语境,也可以作为普通函数使用。
注意:constexpr 函数不一定返回常量表达式!
只有 constexpr 的所有实参都是常量表达式/编译期常量时,constexpr 函数的结果才是常量表达式/编译期常量。只要有一个参数在编译期未知,那就和普通函数一样,在运行时计算。
constexpr int sum(int a, int b) {
return a + b;
}
constexpr int i1 = 42;
constexpr int i2 = sum(i1, 52); // 所有参数都是常量表达式,sum 的结果也是常量表达式,在编译期求值
int AddThree(int i) {
return sum(i, 3); // i 不是常量表达式,此时 sum 作为普通函数使用
}
为了能保证 constexpr 函数在编译时能随时展开计算,constexpr 函数隐式内联。内联函数和 constexpr 函数不同于其他函数,允许定义多次,但要保证所有的定义一致。正因如此,内联函数和 constexpr 函数一般定义在头文件中。
constexpr 限制
因为需要在编译期求值,所以 constexpr 函数有一些限制:返回类型和所有形参的类型必须是字面值类型(literal type)。除了内置类型,用户自定义的类也可以是字面值类型,因为它的构造函数和成员函数也可以是 constexpr 函数。
C++11 中 constexpr 函数还有一些额外限制(C++14 没有这些限制):
- 返回值类型不能是 void
- 函数体内只能有且只有一条 return 语句(但可以用
? :
三目运算符和递归) - 如果是类的成员函数,则为隐式 const 成员函数
1.6 使用 constexpr 的好处
- 编译器可以保证 constexpr 对象是常量表达式(能够在编译期取得结果),而 const 对象不能保证。如果一个 const 变量能够在编译期求值,将其改为 constexpr 能够让代码更清晰易读
- constexpr 函数可以把运行期计算迁移至编译期,使得程序运行更快(但会增加编译时间)
对于常量表达式(编译期值已知),编译器可以进行更多优化,比如放到只读内存中。但这并不是 constexpr 特有的,有的 const 变量也是常量表达式
1.7 小结
- 修饰对象的时候,可以把 constexpr 当作加强版的 const:const 对象只表明值不会改变,不一定能够在编译期取得结果;constexpr 对象不仅值不会改变,而且保证能够在编译期取得结果
- constexpr 函数既可以用于编译期计算,也可以作为普通函数在运行期使用
扩展阅读
-
《C++ Primer 第五版》p58,p214,p267
-
《Effective Modern C++》条款 15:只要有可能使用 constexpr,就使用它
本文作者:Zijian/TENG(微信公众号:好记性如烂笔头),转载请注明原文链接:https://www.cnblogs.com/tengzijian/p/18018104
热门相关:三国之袁氏枭雄 公子风流 盛宠之嫡女医妃 铁血大明 与校花同居:高手风流