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++廢棄,也是完全可以理解的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM