Scott Meyers在effective modern c++中提到“If there were an award for the most confusing new word in C++11, constexpr would probably win it.”c++
因而可知,constexpr确实是比较难以让人理解。加之其在C++11和14中的标准略有不一样,也加重了这种难度。数组
参考几本经典教材(C++ primer, effective modern C++, a tour of C++)以及蓝色大大在知乎上的一些解答,整理出constexpr的用法和注意事项。函数
1.概念,constexpr objects优化
C++ primer中给出的定义是 “常量表达式是指不会改变而且在编译过程当中就能获得计算结果的表达式 【1】。”ui
能够理解为在const上又加一层限定条件,即const并不限定是编译期常量仍是运行期常量,而constexpr必须是编译期常量(在编译阶段获得结果)。spa
举例以下:设计
众所周知,array的size是须要在编译期肯定的,因此当其size不是一个常量表达式时,是没法经过编译的。code
int i; const int size = i; int arr[size]; //error,size不是常量表达式,不能在编译期肯定
而若是size是一个constexpr变量,则符合编译期肯定的条件,能够经过编译。对象
constexpr auto size = 10; int arr[size]; //OK,size时常量表达式
固然,要定义一个常量表达式的时候,也要确保其右侧是常量表达式,不然该处便没法经过编译。blog
int i; constexpr int size = i; // error,i不能在编译期肯定
因此用effective modern c++中的一句话总结这一部分就是:
“constexpr objects are const and are initialized with values known during compilation【2】”.
2. constexpr functions
比起constexpr变量,用constexpr修饰的函数有些更容易混淆的地方。
1) constexpr修饰的函数,当传入参数是能够在编译期计算出来时,产生constexpr变量;
当传入参数不能够在编译期计算出来时,产生运行期遍历(constexpr等于不存在)。
所以,没必要写两个函数,若是函数体存在constexpr适用条件,就应该加上constexpr关键字。
例如(例子来源【3】):
constexpr int foo(int i) { return i + 5; } int main() { int i = 10; std::array<int, foo(5)> arr; // OK,5是常量表达式,计算出foo(5)也是常量表达式 foo(i); // Call is Ok,i不是常量表达式,但仍然能够调用(constexpr 被忽略) std::array<int, foo(i)> arr1; // Error,可是foo(i)的调用结果不是常量表达式了 }
2) 在C++11和14中的区别
在C++11标准中,对于constexpr修饰的函数给了及其苛刻的限定条件:函数的返回值类型及全部形参的类型都是字面值类型,并且函数体内必须有且只有一条return语句【1】。
这个条件显然是太苛刻了,以致于不少在constexpr的操做都要借助?:表达式,递归等办法实现。
在C++14中,放宽了这一限定,只保留了“函数的返回值类型及全部形参的类型都是字面值类型”,也就是说,这些值都在编译期能肯定了就行。
3. constexpr class(字面值常量类)
built-in类型是字面值常量,可是有时须要自定义类型也做为字面值常量,这时候就需须要将constexpr修饰构造函数。
字面值常量类必须至少提供一个constexpr构造函数。
例如:
class Point { public: constexpr Point(double xval = 0, double yval = 0): x(xval), y(yval) { } constexpr double getX() const {return x;} constexpr double getY() const {return y;} private: double x,y; };
当这样定义一个类后,即可以将Point类型的对象定义为字面值常量。即:
constexpr Point p1(9.4, 27,7); constexpr Point p2(28.8, 5.3); constexpr Point midpoint(const Point& p1, const Point& p2) { return {p1.getX() + p2.getX() / 2, p1.getY() + p2.getY() / 2} ; } constexpr auto mid = midpoint (p1, p2);
上述例子中,p1,p2均为字面值常量,midpoint为constexpr修饰的函数,因此求取mid的整个过程均在编译期就能够完成,软件运行的时间天然会大大减小。
至此关于constexpr的三个主要用途(constexpr变量,constexpr修饰函数,constexpr修饰构造函数)就总结完毕,下面是一些注意事项。
注意事项1: 不少人(包括我本身)在gcc中验证数组大小必须在编译期指定的例子时发现:
若是array定义在主函数内,即便给定的不是一个常量表达式,也能够经过编译。这差点颠覆了个人认知。。。
蓝色大大在知乎答案【4】中解释了这一点,实际上是C99中的variable length array。在全局变量中不能使用(没法分配内存),在局部变量中可使用,细节能够参考那份解答。
注意事项2:constexpr这么复杂,到底为何要用?
其实第一仍是为了效率。效率是C++的设计哲学之一,编译期能够肯定的东西,即可以提醒编译期优化,也可能存放在read-only memory中
第二就是这样声明的constexpr变量即可以用在诸如上述数组长度指定,还有包括模板参数,case标签等场合,会便于使用【5】。
参考资料:
1. Stanley B. Lippman / Josée Lajoie / Barbara E. Moo, C++ Primer 中文版(第 5 版)[M]. 电子工业出版社,2013
2. Meyers S. Effective Modern C++[M]. O'Reilly, 2014.
3. 蓝色在知乎问题“C++ const 和 constexpr 的区别?”中的解答: https://www.zhihu.com/question/35614219
4. 蓝色在知乎问题“constexpr和const数组的区别?”中的解答: https://www.zhihu.com/question/29662350/answer/45192834
5. Stroustrup B. A Tour of C++[M]. Addison-Wesley Longman, Amsterdam, 2013.