C++常量表达式(constexpr)
关于常量表达式
关键字constexpr
是在 C++11 引入的,首先和 const
一样可以用于修饰变量,这时候相当于一个加强版的 const
,该变量具备常量属性(也即不可修改),且它的值必须在编译阶段就已知。不同的是,constexpr
还可以用于修饰函数(称为constexpr
函数),这个函数在传入参数都是常量表达式时能够返回编译期常量,若参数在编译期未知时则和普通函数一样在运行期计算。
constexpr 修饰变量
constexpr
修饰变量时,这个变量具备const
属性,也就是不能够修改这个变量的值,其次还说明这个变量的值必须在编译期就已知。这个编译期指的是预处理、编译、链接的整个过程,在编译阶段就已知的变量可以放在只读内存中,可以用于作为数组大小参数等。
1 | int sz = 5; |
再看关键词constexpr
和const
的区别,从语义上来说,const
修饰的变量只说明这个变量的常量性,也就是不可直接修改这个变量的值,并没有保证这个变量一定是在编译期初始化的,而constexpr
则指定了变量必须在编译期初始化。
1 | int sz; |
总的来说,所有constexpr
对象都是const
对象,而const
对象并不一定是constexpr
对象。
constexpr 修饰函数
constexpr
修饰函数必须要有返回值(返回值不能为 void),这样的函数在传入的都是编译期常量的情况下,返回一个编译期常量;在传入的参数中含有非编译期常量的情况下,该函数和普通函数一样在运行时产生一个值。这样免得对同一函数编写constexpr
版本和非constexpr
版本。
例如我们要实现一个计算幂的常量表达式函数:
1 | constexpr int pow(int base, int exp) { |
在 C++ 11 和 C++ 14 中,对使用 constexpr
修饰的函数实现有不同的限制,在 C++11 中限制该函数只能含有一条 return 语句,但是可以使用递归函数,对于上面的例子就可以实现如下:
1 | constexpr int pow(int base, int exp) { |
在 C++14 中,上述标准大大放宽,constexpr
修饰的函数中可以含有循环、条件判断等语句,上述函数可以有一种更易懂的实现:
1 | constexpr int pow(int base, int exp) { |
constexpr
修饰的函数在传入参数都是编译期常量时也会返回一个编译期常量,这个编译期常量包括所有的内置类型(除void外),实际上用户自定义的类型也可以是编译期常量,只需要将这个类的构造函数使用constexpr
进行修饰。
constexpr 修饰构造函数
为了使用户自定义类型成为编译期常量,可以将其构造函数使用constexpr
关键字修饰,只要传入构造函数的参数是编译期常量,那么这个类也是编译期常量:
1 | class Point{ |
只要传入构造函数的参数是编译期常量,那么这个类就是编译期常量:
1 | constexpr Point t(1.0, 2.0); // ok, 编译期常量 |
可以看到,我们将 Point 类的两个访问元素的成员函数也使用了constexpr
修饰,只要类实例是编译期常量,其内部两个成员的值也是编译期常量,这两个函数返回编译期常量也是合情合理。
因此我们可以写出下面返回编译期常量的代码:
1 | constexpr Point midpoint(const Point& p1, const Point& p2) { |
上面的函数涉及了 Point 类的构造、成员函数的访问,却仍然是一个 constexpr
函数,也就是说上述计算过程都可以在编译期就完成,这样的话软件运行速度就会提高。
实际上,在 C++14 中,setX()
和 setY()
函数也可以使用 constexpr
修饰。这里不过多描述了,实际上,代码中使用 constexpr
修饰的代码越多,就会将越多的计算工作放到编译期执行,软件运行时的速度就会越快,当然编译的速度会变慢。
为了追求运行时的速度,可以尽可能的在需要使用编译期常量的情景都使用 constexpr
修饰。
参考文献
- 《Effective Mordern C++》