constexpr函数------c++ primer

  constexpr函数是指能用于常量表达式的函数。定义constexpr函数的方法有其他函数类似,不过要遵循几项约定:函数的返回值类型及所以形参的类型都是字面值类型,而且函数体中必须有且只有一条return语句。为了能在编译过程中随时展开,constexpr函数被隐式指定地指定为内联函数。

  constexpr函数体内也可以包含其他语句,只要这些语句在运行时不执行任何操作就行。例如,constexpr函数中可以有空语句、类型别名以及using声明。

  允许constexpr函数的返回值并非一个常量:

constexpr int scale(int cnt){return 5*cnt;}//如果arg是常量表达式,则scale(arg)也是常量表达式

  当scale的实参是常量表达式时,它的返回值也是常量表达式;反之则不然。如果我们用一个非常量表达式调用scale函数,比如int类型的对象i,则返回值是一个非常量表达式。当把scale函数用在需要常量表达式的上下文时,由编译器负责检查函数的结果是否符合要求。如果结果恰好不是常量表达式,编译器将发出错误信息。constexpr函数不一定返回常量表达式。

  把内联函数和constexpr函数放在头文件内

  和其他函数不一样,内联函数和constexpr函数可以在程序中多次定义。毕竟,编译器要想展开函数仅有函数声明是不够的,还需要函数的定义。不过,对于某个给定的内联函数或者constexpr函数来说,它的多个定义必须完全一致,基于这个原因,内联函数和constexpr函数通常定义在头文件中。

https://www.cufe-ifc.org/question/153643.html

C++ 内联函数和constexpr函数可以在程序中定义不止一次,这个一般用在什么时候?

Tim Shen 2017-08-21  318 内联函数 c++

(C++ Primer 第5版 215页) 而且,书中有说:“对于某个给定的内联函数或者constexpr函数来说,它的多个定义必须完全一致。基于这个原因,内联函数和constexpr函数通常定义在头文件中”。 为什么放在源文件里就不可以了呢? 我用网上的例子(浅谈C++中内联关键字inline)运行了一下: A.h #pragma once class A { public: A(int a, int b) : a(a), b(b) {} int max(); private: int a; int b; }; A.cpp #include "A.h" inlin…

0 0

其他回答

能定义不止一次的好处是方便你放到头文件里,头文件里的好处是每个include这个头文件的.c文件都能函数体,看到函数体的好处是编译器可以内联。内联的好处是代码变快了。另外,所有函数体定义必须一模一样,不然出了问题概不负责。constexpr自带inline属性。

当你下决心在.c文件中定义函数体的时候,自然不需要inline关键字了。而这时候也必须link相应的.o来确保找得到定义。

------------------------------ 话痨版 ------------------------------

首先来几个前置知识:

1) C和C++都有translation unit(或compilation unit)的概念:基本上编译器会一次编译一个文件,然后忘记一切,然后再编译下一个文件。哪怕你写gcc -c a.c b.c,其实和gcc -c a.c && gcc -c b.c大体上是没区别的。在最后,所有的.o文件都被linker汇总link成一个大的可执行文件。

2) static function。你可以把一个函数标记为static(也称为internal linkage),这样该函数的symbol就会被隐藏,从而该函数只存在在当前translation unit。换了下一个translation unit之后,该函数被忘得一干二净。linker也从来不知道这函数存在过。这时候你就算再定义一次上次translation已经定义过的static函数,linker也不会报redefinition错误。当然,这样代码在binary中就出现了多次。

3) 当然你也肯定知道C和C++的include的意思:在A中#include <B>就是把B的内容复制粘贴到A中#include对应的位置。

4) 编译器的内联优化就是看到你在Bar里调用Foo的时候,帮你复制一遍Foo的函数体,内嵌到Bar里去,同时消除栈操作的开销(因为代码已经被复制到“本地”了嘛,不需要跳来跳去了)。内联优化有个缺陷,就是在同一个translation unit里一定要看到函数体,所以光看到declaration是没用的。

现在考虑这么个问题:传统的在头文件中声明,在一个文件(.c)中实现函数体的方式有时执行太慢了。为什么慢呢,假设我这个函数就一行,但是函数调用的压栈传参数弹栈跳转等指令占了大部分开销,真是太不合算了。

这时候在传统C里面有两个解决方案:
1) “宏函数”。就是把本来藏在.c文件里的函数体放到一个宏里面去,当然宏也在头文件里。然后大家include头文件的时候就把宏也include走了,使用宏的时候就把这段代码一遍遍展开到所有使用的地方,消除了函数调用的开销。
2) 在编译器支持内联优化的情况下,在头文件里定义static function。任何别的.c文件,只要include了你的头文件,都对你的头文件做了一次复制粘贴,自动获得了该static function的函数体。所以在不同的translation unit里面,这些函数并不冲突,因为它们是static的。值得一提的是,这些函数体不一定一模一样。举例来说:

// a.h
#define FOO 3
static int Foo() { return FOO; }

// a.c
#include "a.h"

// b.c
#undef FOO
#define FOO 2
#include "a.h"

在不同的translation unit里面一个Foo返回3一个返回2。

1) 的坏处很明显,宏不能解决类型检查的问题,宏是dynamic scope(变量检查环境都是调用端而非定义端的)的,宏是textual substitution,搞不好有迷之编译不通过,宏很丑,定义不带语法高亮(雾),等等。
2) 看上去很好诶,写的是真正的函数,编译器还有能力内联。其缺陷是在编译器决定不内联的时候(通常这时候函数很大),每个translation unit中都定义了一个很大的函数,造成了不必要的binary size bloat。

这时候C++之父Bjarne Stroustrup站出来了,说我们在C++里搞个inline关键字吧!这个关键字不仅编译器认识,而且编译器在没有真正内联该函数时,会通过某种方式提示linker说这个函数被标记为“可重复定义”耶 - 根据我用gcc的实验,生成的是一个weak symbol。当linker看到一个weak symbol,会把函数名写在一个小本本上。在linker最后把所有文件link到一起的时候,它会把小本本扫一遍,对于同名函数只留一个,别的函数连带函数体统统删掉。这样就解决了binary size bloat的问题。当然这只是一种典型实现方式,并非一定如此。

另外,在编译器真正内联了该函数的时候,效果就和static一样了,这也是为什么你的代码里找不到定义 - 因为linker根本看不到static函数。话虽这么说,但是他们不管这个叫internal linkage(inline specifier),因为此时linkage是个implementation detail。语言只是强调:

The definition of an inline function must be present in the translation unit where it is called (not necessarily before the point of call)

在前面提到,用include static functions的方式中include进来的函数体可能不完全一样。inline此处也提到,你要是同名函数的函数体长得不一样,我才不告诉你我要留哪一份删哪几份呢。你要是敢这么做,我不保证我的输出有意义。这个在C++里叫做ODR violation (Definitions and ODR)。编译器一次只看一个translation unit,所以通常是没法检测ODR violation的(不排除LTO还是能查的),而linker也不查,我并不清楚为什么,大概是太昂贵吧。

另外,可以感受下当年inline关键字的marketing口号:
“An Inline Function is As Fast As a Macro”(Inline - Using the GNU Compiler Collection (GCC))

顺带建议一下刷ACM-ICPC的各种坑爹oj的同学,想要速度就用宏,因为oj可能不开内联优化。。

Tim Shen 2017-08-21 14:33:18 0条评论

0 0

对于内联函数而言,其比较大的特点就是会在函数调用的地方被直接展开,而不是一个函数调用,从而减少函数的调用开销。而若是函数调用的话,我们可以在使用函数前的时候,如int foo()的方式,然后让后面的链接器去其它的目标文件进行函数符号的查找。

我来仔细的一步一步的分析这里面产生的缘由,我使用gcc作为演示,因为Linux有一系列的工具可以方便查看,但是这和Visual C++的原理是一致的。

回到你的例子,我们使用g++ -c http://main.cc -o main.o,然后使用nm main.o | c++filt 来查看生成的符号,如下图所示:


我们可以很清晰的看到A::max()的符号是U,即undefined,所以,它希望链接器去其它的地方找到函数的定义。若是一切正常的话,那么我们还有一个A.cpp,然后生成一个a.o,链接器去a.o的地方就可以找到这个函数定义了。

然而我们去编译其实现文件查看其符号表:

我们可以发现,是没有这个符号表的,因为如前文所述,inline函数并不会生成函数调用,会就在本源文件中展开,于是也就没有了函数的符号。

那么正是如此,后面链接器,把产生的.o合并到一起的时候,A::max()依然是未定义的,如下图所示:

那么,若是非内联的情况呢?
我们可以发现,a.o就会产生A::max()的函数符号,这样的话,若链接器去链接main.o a.o :
我们就会发现main.o 带U标记的undefined的A::max()被寻找到了,并在最后的.o中进行了填充。

那么,为什么inline的函数定义放在头文件就可以呢?因为我们使用的时候,会#include "a.h",那么这个时候编译器首先会预处理展开,这样也就包括了A::max()的定义,使用g++ -E http://a.cc > a.i; vim a.i 后如下图所示:

那么这个时候,http://main.cc在本源文件就可以找到A::max()的定义了,然后让我们看最后的符号表:

也的确如我们意想的不是U的undefined。

那么,这里多提一句的是,在现代的C++编译器中,我们几乎都做一个优化叫做内联函数优化,因为我们在优化的时候,若不是IPA这样的都是以一个函数来进行,那么我们内联函数优化后就会把相关的定义展开在一个函数中,这样就可以进行更好的优化。而这个内联函数的优化级别,在每一个编译器中是不同的,但是在我们实际C++编译器的实现中,其实已经完全不是很多教科书提到的几行了,我们往往是100行,乃至更多行的函数都会内联展开,就是为了更好的优化。而我们内联优化的话,其实也就是根据你的函数是多少行来进行决定的,于是在现代C++编译器中,如果你是为了inline优化而加上inline的话,我认为inline的意义在这里已经完全不需要了,现代的C++编译器优化已经非常强悍了。所以,若将来inline被C++废弃,也是完全可以理解的。

原文地址:https://www.cnblogs.com/l2017/p/9379673.html

时间: 2024-11-06 09:09:04

constexpr函数------c++ primer的相关文章

【C++ Primer 第六章】 constexpr函数

constexpr函数 constexpr函数: constexpr函数是指用于常量表达式的函数,函数的返回值类型以及所有的形参类型必须是字面值,而且函数必须有且只有一条return语句. 1 [tect2.cpp] 2 #include <iostream> 3 using namespace stdl; 4 5 constexpr int screen(int x) // constexpr 6 { 7 return x; 8 } 9 10 int main() 11 { 12 const

特殊用途语言特性:默认实参,内联函数和constexpr函数,调试帮助

重点: 1.三种函数相关的语言特性:默认实参,内联函数和constexpr函数. 2.默认实参:某些函数有一种形参,在函数的很多次调用中它们都被赋予一个相同的值. 3.一旦某个形参被赋予了默认值之后,它后面的所以形参都必须要默认值. 4.若想使用默认形参,只要在调用函数时省略该实参即可. Tip:Window = screen ( , , ‘?’ );//错误:只能省略尾部的实参! 5.对于函数的声明来说,习惯将其放在头文件当中,在给定的作用域中一个形参只能赋予一次默认实参. NOTE:通常,应

C++11 constexpr函数

constexpr函数是指能用于常量表达式的函数,定义constexpr的方式和其他函数的定义方式一样,但存在下面两个约束: 1.函数的返回值必须为字面值常量: 2.函数体中必须且只有一个return语句: constexpr int new_sz(){return 42;} constexpr函数时被隐式地指定为内联函数的. constexpr函数体内也可以有其他的语句,只要运行时不执行任何操作即可,比如空语句,类型别名,using声明:我们允许constexpr函数的返回值并非一个常量: c

聚合类,字面值类型,constexpr函数

税6q疚E扒Co挖豪http://www.zcool.com.cn/collection/ZMTg0NDc2NjA=.html 卫8肺4抖8回咏6xNDThttp://www.zcool.com.cn/collection/ZMTg0NDc3OTI=.html 493械肆1x97HXN沮http://www.zcool.com.cn/collection/ZMTg0NDg1NjQ=.html 魏材l只焚峡51燎PG51http://www.zcool.com.cn/collection/ZMTg

c++ primer(第五版)学习笔记及习题答案代码版(第六章)函数

笔记较为零散,都是自己不熟悉的知识点. 习题答案至于一个.cc 中,编译需要包含Chapter6.h头文件. 需要演示某一题直接修改 #define NUM***, 如运行6.23题为#define NUM623: chapter 6 1. 形参初始化的机理与变量初始化一样. 当形参是引用类型时,它对应的实参被引用传递或者函数被传引用调用. 2. const和实参 void fcn(const int i){ /*fcn能够读取i,但是不能向i写值*/} void fcn(int i){ /*.

C++之内联函数与constexpr

inline 函数 规模小,流程直接且频繁调用 cout<<shortString(s1,s2)<<endl; = cout<<(s1.size()<s2.size()?s1:s2)<<endl; constexpr函数是指能用于常量表达式的函数.函数的返类型及所有形参的类型都得是字面值类型,而且函数体中必须有一条return语句:

C++ Primer 笔记——函数

1.函数内的局部静态对象在程序的执行路径第一次经过对象定义语句的时候初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响. size_t get_count() { static size_t count = 0; return ++count; } 2.当实参初始化形参的时候会忽略顶层const,换句话说,当形参有顶层const时,传给它常量或者非常量都是可以的. const int ci = 1; // ci的值不能被改变,const是顶层的 int i =

&lt;&lt;C++ Primer&gt;&gt; 第 6 章 函数

术语表 第 6 章 函数 二义性调用(ambiguous call): 是一种编译时发生的错误,造成二义性调用的原因时在函数匹配时两个或多个函数提供的匹配一样好,编译器找不到唯一的最佳匹配. ?? 实参(argument): 函数调用时提供的值,用于初始化函数的形参. ?? Assert: 是一个预处理宏,作用于一条表示条件的表达式.当未定义预处理遍历NDEBUG时,assert对条件求值.如果条件为假,输出一条错误信息并终止当前程序的执行. ?? 自动对象(automatic object):

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