在C++中咱們一般定義如下函數來求兩個整數的最大值:ios
int max(int a, int b) { return a > b ? a : b; }
爲這麼一個小的操做定義一個函數的好處有:緩存
① 閱讀和理解函數 max 的調用,要比讀一條等價的條件表達式並解釋它的含義要容易得多函數
② 若是須要作任何修改,修改函數要比找出並修改每一處等價表達式容易得多性能
③ 使用函數能夠確保統一的行爲,每一個測試都保證以相同的方式實現測試
④ 函數能夠重用,沒必要爲其餘應用程序重寫代碼this
雖然有這麼多好處,可是寫成函數有一個潛在的缺點:調用函數比求解等價表達式要慢得多。在大多數的機器上,調用函數都要作不少工做:調用前要先保存寄存器,並在返回時恢復,複製實參,程序還必須轉向一個新位置執行編碼
C++中支持內聯函數,其目的是爲了提升函數的執行效率,用關鍵字 inline 放在函數定義(注意是定義而非聲明,下文繼續講到)的前面便可將函數指定爲內聯函數,內聯函數一般就是將它在程序中的每一個調用點上「內聯地」展開,假設咱們將 max 定義爲內聯函數:spa
inline int max(int a, int b) { return a > b ? a : b; }
則調用: cout<<max(a, b)<<endl;設計
在編譯時展開爲: cout<<(a > b ? a : b)<<endl;blog
從而消除了把 max寫成函數的額外執行開銷
不管是《Effective C++》中的 「Prefer consts,enums,and inlines to #defines」 條款,仍是《高質量程序設計指南——C++/C語言》中的「用函數內聯取代宏」,宏在C++中基本是被廢了,在書《高質量程序設計指南——C++/C語言》中這樣解釋到:
關鍵字 inline 必須與函數定義體放在一塊兒才能使函數成爲內聯,僅將 inline 放在函數聲明前面不起任何做用。
以下風格的函數 Foo 不能成爲內聯函數:
inline void Foo(int x, int y); // inline 僅與函數聲明放在一塊兒 void Foo(int x, int y) { ... }
而以下風格的函數 Foo 則成爲內聯函數:
void Foo(int x, int y); inline void Foo(int x, int y) // inline 與函數定義體放在一塊兒 { ... }
因此說,C++ inline函數是一種「用於實現的關鍵字」,而不是一種「用於聲明的關鍵字」。通常地,用戶能夠閱讀函數的聲明,可是看不到函數的定義。儘管在大多數教科書中內聯函數的聲明、定義體前面都加了 inline 關鍵字,但我認爲 inline 不該該出如今函數的聲明中。這個細節雖然不會影響函數的功能,可是體現了高質量C++/C 程序設計風格的一個基本原則:聲明與定義不可混爲一談,用戶沒有必要、也不該該知道函數是否須要內聯。
定義在類聲明之中的成員函數將自動地成爲內聯函數,例如:
class A { public: void Foo(int x, int y) { ... } // 自動地成爲內聯函數 }
可是編譯器是否將它真正內聯則要看 Foo函數如何定義
內聯函數應該在頭文件中定義,這一點不一樣於其餘函數。編譯器在調用點內聯展開函數的代碼時,必須可以找到 inline 函數的定義才能將調用函數替換爲函數代碼,而對於在頭文件中僅有函數聲明是不夠的。
固然內聯函數定義也能夠放在源文件中,但此時只有定義的那個源文件能夠用它,並且必須爲每一個源文件拷貝一份定義(即每一個源文件裏的定義必須是徹底相同的),固然即便是放在頭文件中,也是對每一個定義作一份拷貝,只不過是編譯器替你完成這種拷貝罷了。但相比於放在源文件中,放在頭文件中既可以確保調用函數是定義是相同的,又可以保證在調用點可以找到函數定義從而完成內聯(替換)。
可是你會很奇怪,重複定義那麼屢次,不會產生連接錯誤?
咱們來看一個例子:
//A.h : class A { public: A(int a, int b) : a(a),b(b){} int max(); private: int a; int b; }; //A.cpp : #include "A.h" inline int A::max() { return a > b ? a : b; } //Main.cpp : #include <iostream> #include "A.h" using namespace std; inline int A::max() { return a > b ? a : b; } int main() { A a(3, 5); cout<<a.max()<<endl; return 0; }
一切正常編譯,輸出結果:5
假若你在Main.cpp中沒有定義max內聯函數,那麼會出現連接錯誤:
error LNK2001: unresolved external symbol "public: int __thiscall A::max(void)" (?max@A@@QAEHXZ)main.obj
找不到函數的定義,因此內聯函數能夠在程序中定義不止一次,只要 inline 函數的定義在某個源文件中只出現一次,並且在全部源文件中,其定義必須是徹底相同的就能夠。
在頭文件中加入或修改 inline 函數時,使用了該頭文件的全部源文件都必須從新編譯。
4. 慎用內聯
內聯雖有它的好處,可是也要慎用,如下摘自《高質量程序設計指南——C++/C語言》:
而在Google C++編碼規範中則規定得更加明確和詳細:
內聯函數:
Tip: 只有當函數只有 10 行甚至更少時纔將其定義爲內聯函數.
定義: 當函數被聲明爲內聯函數以後, 編譯器會將其內聯展開, 而不是按一般的函數調用機制進行調用.
優勢: 當函數體比較小的時候, 內聯該函數能夠令目標代碼更加高效. 對於存取函數以及其它函數體比較短, 性能關鍵的函數, 鼓勵使用內聯.
缺點: 濫用內聯將致使程序變慢. 內聯可能使目標代碼量或增或減, 這取決於內聯函數的大小. 內聯很是短小的存取函數一般會減小代碼大小, 但內聯一個至關大的函數將戲劇性的增長代碼大小. 現代處理器因爲更好的利用了指令緩存, 小巧的代碼每每執行更快。
結論: 一個較爲合理的經驗準則是, 不要內聯超過 10 行的函數. 謹慎對待析構函數, 析構函數每每比其表面看起來要更長, 由於有隱含的成員和基類析構函數被調用!
另外一個實用的經驗準則: 內聯那些包含循環或 switch 語句的函數經常是得不償失 (除非在大多數狀況下, 這些循環或 switch 語句從不被執行).
有些函數即便聲明爲內聯的也不必定會被編譯器內聯, 這點很重要; 好比虛函數和遞歸函數就不會被正常內聯. 一般, 遞歸函數不該該聲明成內聯函數.(遞歸調用堆棧的展開並不像循環那麼簡單, 好比遞歸層數在編譯時多是未知的, 大多數編譯器都不支持內聯遞歸函數). 虛函數內聯的主要緣由則是想把它的函數體放在類定義內, 爲了圖個方便, 抑或是看成文檔描述其行爲, 好比精短的存取函數.
-inl.h文件:
Tip: 複雜的內聯函數的定義, 應放在後綴名爲 -inl.h 的頭文件中.
內聯函數的定義必須放在頭文件中, 編譯器才能在調用點內聯展開定義. 然而, 實現代碼理論上應該放在 .cc 文件中, 咱們不但願 .h 文件中有太多實現代碼, 除非在可讀性和性能上有明顯優點.
若是內聯函數的定義比較短小, 邏輯比較簡單, 實現代碼放在 .h 文件裏沒有任何問題. 好比, 存取函數的實現理所固然都應該放在類定義內. 出於編寫者和調用者的方便, 較複雜的內聯函數也能夠放到 .h 文件中, 若是你以爲這樣會使頭文件顯得笨重, 也能夠把它萃取到單獨的 -inl.h 中. 這樣把實現和類定義分離開來, 當須要時包含對應的 -inl.h 便可。