constexpr:编译期与运行期之间的神秘关键字
Scott Meyers在effective modern c++中提到“If there were an award for the most confusing new word in C++11, constexpr would probably win it.”
由此可见,constexpr确实是比较难以让人理解。加之其在C++11和14中的标准略有不同,也加剧了这种难度。
参考几本经典教材(C++ primer, effective modern C++, a tour of C++)以及蓝色大大在知乎上的一些解答,整理出constexpr的用法和注意事项。
1.概念,constexpr objects
C++ primer中给出的定义是 “常量表达式是指不会改变并且在编译过程中就能得到计算结果的表达式 【1】。”
可以理解为在const上又加一层限定条件,即const并不限定是编译期常量还是运行期常量,而constexpr必须是编译期常量(在编译阶段得到结果)。
举例如下:
众所周知,array的size是需要在编译期确定的,所以当其size不是一个常量表达式时,是无法通过编译的。
int i;
const int size = i;
int arr[size]; //error,size不是常量表达式,不能在编译期确定
而如果size是一个constexpr变量,则符合编译期确定的条件,可以通过编译。
constexpr auto size = ;
int arr[size]; //OK,size时常量表达式
当然,要定义一个常量表达式的时候,也要确保其右侧是常量表达式,否则该处便无法通过编译。
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 + ;
}
int main() {
int i = ;
std::array<int, foo()> 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 = , double yval = ): 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, ,);
constexpr Point p2(28.8, 5.3); constexpr
Point midpoint(const Point& p1, const Point& p2) {
return {p1.getX() + p2.getX() / , p1.getY() + p2.getY() / } ;
} 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.
constexpr:编译期与运行期之间的神秘关键字的更多相关文章
- c++ 编译期与运行期
分享到 一键分享 QQ空间 新浪微博 百度云收藏 人人网 腾讯微博 百度相册 开心网 腾讯朋友 百度贴吧 豆瓣网 搜狐微博 百度新首页 QQ好友 和讯微博 更多... 百度分享 转自:http://h ...
- 深入分析Java的编译期与运行期
不知大家有没有思考过,当我们使用IDE写了一个Demo类,并执行main函数打印 hello world时都经历了哪些流程么? 想通过这篇文章来分析分析Java的执行流程,或者换句话说想聊聊Java的 ...
- Java编译期与运行期
编译期:是指把源码交给编译器编译成计算机可以执行的文件的过程.在Java中也就是把Java代码编成class文件的过程.编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成文本 ...
- c++ 之 编译期多态&运行期多态
编译时多态:程序运行前发生的事件 —— 函数重载.运算符重载 .模板 ——静态绑定 运行时多态:程序运行时发生的事件 —— 虚函数机制——动态绑定 template<typename T> ...
- 深入理解Java虚拟机读书笔记7----晚期(运行期)优化
七 晚期(运行期)优化 1 即时编译器(JIT编译器) ---当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”,包括被多次调用的方法和被多次执行的循环体. ...
- java在编译期和运行期都做了什么
Java对象内存存储,引用传递,值传递详细图解 java对象在内存中的分配 编译过程: 编译器把一种语言规范转化为另一种语言规范的这个过程需要哪些步骤?回答这个问题需要参照<编译原理>,总 ...
- Javac早期(编译期)
从Sun Javac的代码来看,编译过程大致可以分为3个过程: 解析与填充符号表过程. 插入式注解处理器的注解处理过程. 分析与字节码生成过程. Javac编译动作的入口是com.sun.tools. ...
- JVM总结(六):早期(编译期)优化
这节我们来总结一下JVM编译器优化问题. JVM编译器优化 Javac编译器 Javac的源码和调试 解析与填充符号表 注解处理器 语法分析与字节码生成 Java语法糖 泛型和类型擦除 自动装箱.拆箱 ...
- 深入理解JVM - 早期(编译期)优化
Java“编译期”是一段“不确定”的操作过程:可能是指一个前端编译器(编译器的前端)把*.java文件转变为*.class文件的过程:可能是指虚拟机的后端运行期编译器(JIT编译器,Just In T ...
随机推荐
- Leetcode438.Find All Anagrams in a String找到字符串中所有字母异位词
给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引. 字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100. 说明: ...
- LUOGU P2441 角色属性树
题目描述 绪萌同人社是一个有趣的组织,该组织结构是一个树形结构.有一个社长,直接下属一些副社长.每个副社长又直接下属一些部长--. 每个成员都有一个萌点的属性,萌点属性是由一些质数的萌元素乘积构成(例 ...
- bzoj 3029 守卫者的挑战——概率期望dp+状态数思考
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3029 先随便写了个dfs,记录“前 i 次.成功 j 次.容量-残片=k”的概率.因为是否可 ...
- TZ_11_Spring-Boot的入门
1.SpringBoot的特点 为基于Spring的开发提供更快的入门体验 开箱即用,没有代码生成,也无需XML配置.同时也可以修改默认值来满足特定的需求 提供了一些大型项目中常见的非功能性特性,如嵌 ...
- Vue-cli3.x在开发环境中(router采用 history模式)出现Failed to resolve async component default: Error: Loading chunk {/d} failed.或者Uncaught SyntaxError: Unexpected token <错误
使用Vue-cli3.x开发环境中(router采用 history模式)出现Failed to resolve async component default: Error: Loading chu ...
- 为什么要使用ul li布局网站导航条?使用ul li布局网站网页导航必要性
会布局的都知道网站导航条布局非常重要,可能一个导航条最终布局效果有时可以使用ul li列表标签布局,有时可以不用ul li布局,而是直接一个div盒子里直接放锚文本超链接的栏目名称,也能实现,看下图. ...
- top进程命令
top命令用来显示系统当前的进程状况. 格式:top [选项] 主要选项如下. d:指定更新的间隔,以秒计算. q:没有任何延迟的更新.如果使用者有超级用户,则top命令将会以最高的优先序执行. c: ...
- NoSQL最新现状和趋势:云NoSQL数据库将成重要增长引擎
NoSQL最早起源于1998年,但从2009年开始,NoSQL真正开始逐渐兴起和发展.回望历史应该说NoSQL数据库的兴起,完全是十年来伴随互联网技术,大数据数据的兴起和发展,NoSQL在面临大数据场 ...
- Windows 调用OpenProcess失败
OpenProcess 打开|获得进程句柄 函数原型: HANDLE OpenProcess(DWORD dwDesiredAccess,BOOL bInheritHandle,DWORD dwPro ...
- SpringBoot启动报错Failed to determine a suitable driver class
SpringBoot启动报错如下 Error starting ApplicationContext. To display the conditions report re-run your app ...