引入
C++11 为了提高代码执行效率做了一些改善。这种改善之一就是:生成常量表达式,允许程序利用编译时的计算能力。 假如你熟悉模板元编程,你将发现 constexpr 使这一切变得更加简单。constexpr 使我们很容易利用上编译时编程的优势。
常量表达式主要是允许一些计算发生在编译时,即发生在代码编译而不是运行的时候。这是很大的优化:假如有些事情可以在编译时做,它将只做一次,而不是每次程序运行时。
需要计算一个编译时已知的常量,比如特定值的 sine 或 cosin ?确实你亦可以使用库函数 sin 或 cos,但那样你必须花费运行时的开销。使用 constexpr,你可以创建一个编译时的函数,它将为你计算出你需要的数值,用户的电脑将不需要做这些工作。
constexpr 初识
为了使函数获取编译时计算的能力,你必须指定 constexpr 关键字到这个函数。
constexpr int multiply (int x, int y)
{
return x * y;
}
// 将在编译时计算
const int val = multiply( 10, 10 );
除了编译时计算的性能优化,constexpr 的另外一个优势是,它允许函数被应用在以前调用宏的所有场合。
例如,你想要一个计算数组 size 的函数,size 是 10 的倍数。如果不用 constexpr,你需要创建一个宏或者使用模板,因为你不能用函数的返回值去声明数组的大小。但是用 constexpr,你就可以调用一个 constexpr 函数去声明一个数组。
constexpr int getDefaultArraySize (int multiplier)
{
return 10 * multiplier;
}
int my_array[getDefaultArraySize(3)];
int a = 4; //非常量表达式
getDefaultArraySize(a); //ok
constexpr 修饰的函数,简单的来说,如果其传入的参数可以在编译时期计算出来,那么这个函数就会产生编译时期的值。但是,传入的参数如果不能在编译时期计算出来,那么 constexpr 修饰的函数就和普通函数一样了。 不过,我们不必因此而写两个版本,所以如果函数体适用于 constexpr 函数的条件,可以尽量加上 constexpr。
constexpr 修饰函数的限制
一个 constexpr 有一些必须遵循的严格要求:
- 函数中只能有一个 return 语句(但允许包含 typedefs、 using declaration && directives、静态断言等)
- 函数必须有返回值(即不能式void函数)
- 只能调用其它 constexpr 函数
- 只能使用全局 constexpr 变量
注意递归并不受限制,但只允许一个返回语句,那如何实现递归呢?可以使用三元运算符( ? : )。例如,计算n的阶乘:
constexpr int factorial (int n) {
return n > 0 ? n * factorial( n - 1 ) : 1;
}
现在你可以使用 factorial(2),编译器将在编译时计算这个值,这种方式运行更巧妙的计算,与内联截然不同。你无法内联一个递归函数。
constexpr vs const 的区别
假如你将一个成员函数标记为 constexpr,则顺带也将它标记为了 const。如果你将一个变量标记为 constexpr,则同样它是 const 的。但相反并不成立,一个 const 的变量或函数,并不是 constexpr 的。
语义上:
- constexpr:告诉编译器我可以是编译期间可知的,尽情的优化我吧。
- const:告诉程序员没人动得了我,放心的把我传出去;或者放心的把变量交给我,我啥也不动就瞅瞅。
语法上:
- constexpr 是一种比 const 更严格的束缚,它修饰的表达式本身在编译期间可知,并且编译器会尽可能的 evaluate at compile time。在constexpr 出现之前,可以在编译期初始化的const都是implicit constexpr。直到 C++ 11,constexpr 才从 const 中细分出来成为一个关键字,而 const 从 1983 年 C++ 刚改名的时候就存在了… 如果你初学 C++, 应当尽可能的,合理的使用 constexpr 来帮助编译器优化代码。