天天看點

轉:C++ 關鍵字 inline詳細介紹1.  内聯函數2.  内聯函數和宏3.  将内聯函數放入頭檔案

轉:C++ 關鍵字 inline詳細介紹

1.  内聯函數

在C++中我們通常定義以下函數來求兩個整數的最大值:

int max(int a, int b)
{
     return a > b ? a : b;
}
           

  

為這麼一個小的操作定義一個函數的好處有:

① 閱讀和了解函數 max 的調用,要比讀一條等價的條件表達式并解釋它的含義要容易得多

② 如果需要做任何修改,修改函數要比找出并修改每一處等價表達式容易得多

③ 使用函數可以確定統一的行為,每個測試都保證以相同的方式實作

④ 函數可以重用,不必為其他應用程式重寫代碼

雖然有這麼多好處,但是寫成函數有一個潛在的缺點:調用函數比求解等價表達式要慢得多。在大多數的機器上,調用函數都要做很多工作:調用前要先儲存寄存器,并在傳回時恢複,複制實參,程式還必須轉向一個新位置執行

C++中支援内聯函數,其目的是為了提高函數的執行效率,用關鍵字 inline 放在函數定義(注意是定義而非聲明,下文繼續講到)的前面即可将函數指定為内聯函數,内聯函數通常就是将它在程式中的每個調用點上“内聯地”展開,假設我們将 max 定義為内聯函數:

inline int max(int a, int b)
{
     return a > b ? a : b;
}
           

  

則調用: cout<<max(a, b)<<endl;

在編譯時展開為: cout<<(a > b ? a : b)<<endl;

進而消除了把 max寫成函數的額外執行開銷

2.  内聯函數和宏

無論是《Effective C++》中的 “Prefer consts,enums,and inlines to #defines” 條款,還是《高品質程式設計指南——C++/C語言》中的“用函數内聯取代宏”,宏在C++中基本是被廢了,在書《高品質程式設計指南——C++/C語言》中這樣解釋到:

轉:C++ 關鍵字 inline詳細介紹1.  内聯函數2.  内聯函數和宏3.  将内聯函數放入頭檔案

3.  将内聯函數放入頭檔案

關鍵字 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)" ([email protected]@@QAEHXZ)main.obj

找不到函數的定義,是以内聯函數可以在程式中定義不止一次,隻要 inline 函數的定義在某個源檔案中隻出現一次,而且在所有源檔案中,其定義必須是完全相同的就可以。

在頭檔案中加入或修改 inline 函數時,使用了該頭檔案的所有源檔案都必須重新編譯。

4.  慎用内聯

内聯雖有它的好處,但是也要慎用,以下摘自《高品質程式設計指南——C++/C語言》:

轉:C++ 關鍵字 inline詳細介紹1.  内聯函數2.  内聯函數和宏3.  将内聯函數放入頭檔案

而在Google C++編碼規範中則規定得更加明确和詳細:

内聯函數:

Tip: 隻有當函數隻有 10 行甚至更少時才将其定義為内聯函數.

定義: 當函數被聲明為内聯函數之後, 編譯器會将其内聯展開, 而不是按通常的函數調用機制進行調用.

優點: 當函數體比較小的時候, 内聯該函數可以令目标代碼更加高效. 對于存取函數以及其它函數體比較短, 性能關鍵的函數, 鼓勵使用内聯.

缺點: 濫用内聯将導緻程式變慢. 内聯可能使目标代碼量或增或減, 這取決于内聯函數的大小. 内聯非常短小的存取函數通常會減少代碼大小, 但内聯一個相當大的函數将戲劇性的增加代碼大小. 現代處理器由于更好的利用了指令緩存, 小巧的代碼往往執行更快。

結論: 一個較為合理的經驗準則是, 不要内聯超過 10 行的函數. 謹慎對待析構函數, 析構函數往往比其表面看起來要更長, 因為有隐含的成員和基類析構函數被調用!

另一個實用的經驗準則: 内聯那些包含循環或 switch 語句的函數常常是得不償失 (除非在大多數情況下, 這些循環或 switch 語句從不被執行).

有些函數即使聲明為内聯的也不一定會被編譯器内聯, 這點很重要; 比如虛函數和遞歸函數就不會被正常内聯. 通常, 遞歸函數不應該聲明成内聯函數.(遞歸調用堆棧的展開并不像循環那麼簡單, 比如遞歸層數在編譯時可能是未知的, 大多數編譯器都不支援内聯遞歸函數). 虛函數内聯的主要原因則是想把它的函數體放在類定義内, 為了圖個友善, 抑或是當作文檔描述其行為, 比如精短的存取函數.

-inl.h檔案:

Tip: 複雜的内聯函數的定義, 應放在字尾名為 -inl.h 的頭檔案中.

内聯函數的定義必須放在頭檔案中, 編譯器才能在調用點内聯展開定義. 然而, 實作代碼理論上應該放在 .cc 檔案中, 我們不希望 .h 檔案中有太多實作代碼, 除非在可讀性和性能上有明顯優勢.

如果内聯函數的定義比較短小, 邏輯比較簡單, 實作代碼放在 .h 檔案裡沒有任何問題. 比如, 存取函數的實作理所當然都應該放在類定義内. 出于編寫者和調用者的友善, 較複雜的内聯函數也可以放到 .h 檔案中, 如果你覺得這樣會使頭檔案顯得笨重, 也可以把它萃取到單獨的 -inl.h 中. 這樣把實作和類定義分離開來, 當需要時包含對應的 -inl.h 即可。

posted on 2015-05-07 11:46  過河的小兵 閱讀( ...) 評論( ...) 編輯 收藏

轉載于:https://www.cnblogs.com/eddyshn/p/4484396.html