站点介绍
C++11有许多改善特性,使得程序可以运行的更快。其中之一就是常量表示式,令程序可以重复利用编译期计算。如果你熟悉模板元编程,常量表示式看起来像一种令你更方便工作的工具。如果你不熟悉模板元编程,没有任何关系,常量表示式使得你更容易利用编译期计算能力。
常量表示式的基本思想是让确定可计算结果在编译期进行计算(就是代码被编译的时候),而不是程序运行的时候。这有一个明显的性能功率提升:如果一些工作可以在编译器完成,他只要计算一次,而不是每次运行驶路程序的过程中,都计算一次。假设咱们需要计算特定值的sin或者cos,当然你可以使用库函数进行计算,但是你付出的代价就是,运行期函数调用。使用常量表示式,你可以创建一个函数,在编译期为你计算这个值。
基本使用方法为了在编译期处理,你需要使用constexpr关键字:
常量表示式的另一大用处,你可以在原来使用的宏的地方,用这个函数代替。例如,你想要定义一个函数,基于一个乘子计算数组的长度。在之前的C++版本,你只能为此创建一个宏或者使用模板元编程的方式,因为不能使用函数调用的结果声明数组的长度。但是使用常亮表示式,你可以在数组声明的地方调用constexpr函数:
constexpr函数的限制一个constexpr函数有一些非常严格的限制:
只能由一个return语句组成(有几个例外)
只能调用其他constexpr函数
只能引用constexpr全局变量
注意,递归是没有被限制的。你如何在只由一个return语句组成的函数里使用递归呢?可以使用三元操作符(冒号问号操作符)。例如,下面是一个计算阶乘函数:
现在,你可以使用factorial(2),编译器在看到这个调用的时候,就会在编译期计算它。通过这种方式,constexpr可以进行更发杂的运算,constexpr函数表现的也跟纯粹的inline函数不一样,inline函数不允许递归!
constexpr函数中还可以做什么?
constexpr函数只能有一行可执行代码,但是它还可以包含其他内容:类型定义(typedef),using声明,指令(directive),静态断言(static_assert)。
constexpr和运行时一个函数被constexpr修饰,它仍然可以在运行时被调用,此时函数参数不是常量:
这意味着,你不需要为编译期和运行时分别定义函数。
编译期对象因为constexpr函数引用的任何变量都一定是constexpr的,那怎么才能利用对象呢?例如,怎么使用一个圆对象?
然后你希望可以像下面那样编译器获得圆对象,并且计算它的面积:
说实话,仅仅需要对Circle类做一些简单的改动,你就可以这样做。首先,咱们需要声明构造函数为constexpr,然后,咱们需要声明getArea函数为constexpr。将构造函数声明为constexpr,允许他在编译器运行,前提是他只包含初始化列表,且初始化使用其他constexpr构造函数。
constexpr 对比 const如果你将一个成员函数声明为constexpr,他也被认为是const的。(很明显,constexpr必定const,因为constexpr函数不能改写对象。)如果你将一个变量声明为constexpr,他也被认为是const的。但是反过来,都不成立。
constexpr和浮点数到目前为止,咱们看到的constexpr功能都可以用模板元编程的方式实现(虽然非常的复杂)。但是,使用constexpr有一个全新的能力,编译期计算浮点数。因为,无类型参数模板不支持浮点数,你很难使用模板元编程的方法在编译期计算浮点数。
另外C++长期因相当长的编译期时耗而备受诟病;constexpr是个高效的工具,但是他另外引入了编译期时耗。然而,有一些內建的特性可以优化这种时耗。首先,因为constexpr函数总是返回一样的输出值,他们可以被持久化,事实上GCC已经支持了memoization特性。
因为constexpr函数可以被持久化,用constexpr代替模板元编程,性能上不会更糟糕,而且易读性更有提升。再算上模板大量实例化的时耗的话,constexpr的性能反而更好。
最后,C++11标准允许编译器限制constexpr函数的最大递归(最少支持512层)。这一约束,防止的极端情况下的严重性能损耗。