码迷,mamicode.com
首页 > 编程语言 > 详细

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

时间:2018-07-27 22:27:08      阅读:212      评论:0      收藏:0      [点我收藏+]

标签:span   传统   内联函数   内联   获得   comm   inter   func   rup   

  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…
其他回答
能定义不止一次的好处是方便你放到头文件里,头文件里的好处是每个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条评论 
对于内联函数而言,其比较大的特点就是会在函数调用的地方被直接展开,而不是一个函数调用,从而减少函数的调用开销。而若是函数调用的话,我们可以在使用函数前的时候,如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++废弃,也是完全可以理解的。

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

标签:span   传统   内联函数   内联   获得   comm   inter   func   rup   

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

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!