內聯函數的聲明和定義


一、什么叫inline函數? 
inline(小心,不是online),翻譯成“內聯”或“內嵌”。意指:當編譯器發現某段代碼在調用一個內聯函數時,它不是去調用該函數,而是將該函數的代碼,整段插入到當前位置。這樣做的好處是省去了調用的過程,加快程序運行速度。函數的調用過程,由於有前面所說的參數入棧等操作,所以總要多占用一些時間)。這樣做的不好處:由於每當代碼調用到內聯函數,就需要在調用處直接插入一段該函數的代碼,所以程序的體積將增大。拿生活現象比喻,就像電視壞了,通過電話找修理工來,你會嫌慢,於是干脆在家里養了一個修理工。這樣當然是快了,不過,修理工住在你家可就要占地兒了。內聯函數並不是必須的,它只是為了提高速度而進行的一種修飾。要修飾一個函數為內聯型,使用如下格式: 
inline 函數的聲明或定義 
簡單一句話,在函數聲明或定義前加一個 inline 修飾符。 
inline int max(int a, int b) 

return (a>b)? a : b; 
}

內聯函數的本質是,節省時間但是消耗空間。

二、inline函數的規則

(1)、一個函數可以自已調用自已,稱為遞歸調用(后面講到),含有遞歸調用的函數不能設置為inline;

(2)、使用了復雜流程控制語句:循環語句和switch語句,無法設置為inline;

(3)、由於inline增加體積的特性,所以建議inline函數內的代碼應很短小。最好不超過5行。

(4)、inline僅做為一種“請求”,特定的情況下,編譯器將不理會inline關鍵字,而強制讓函數成為普通函數。出現這種情況,編譯器會給出警告消息。

(5)、在你調用一個內聯函數之前,這個函數一定要在之前有聲明或已定義為inline,如果在前面聲明為普通函數,而在調用代碼后面才定義為一個inline函數,程序可以通過編譯,但該函數沒有實現inline。比如下面代碼片段: 
//函數一開始沒有被聲明為inline: 
void foo(); 
//然后就有代碼調用它: 
foo(); 
//在調用后才有定義函數為inline: 
inline void foo() 

...... 

代碼是的foo()函數最終沒有實現inline;

(6)、為了調試方便,在程序處於調試階段時,所有內聯函數都不被實現。

三、使用內聯函數時應注意以下幾個問題:

(1) 在一個文件中定義的內聯函數不能在另一個文件中使用。它們通常放在頭文件中共享。 
(2) 內聯函數應該簡潔,只有幾個語句,如果語句較多,不適合於定義為內聯函數。 
(3) 內聯函數體中,不能有循環語句、if語句或switch語句,否則,函數定義時即使有inline關鍵字,編譯器也會把該函數作為非內聯函數處理。 
(4) 內聯函數要在函數被調用之前聲明。關鍵字inline 必須與函數定義體放在一起才能使函數成為內聯,僅將inline 放在函數聲明前面不起任何作用。

定義在類聲明之中的成員函數將自動地成為內聯函數

例如

class A

{

public:void Foo(int x, int y) { } // 自動地成為內聯函數

}

將成員函數的定義體放在類聲明之中雖然能帶來書寫上的方便,但不是一種良好的編程風格,上例應該改成:

// 頭文件

class A

{

public:

void Foo(int x, int y);

}

// 定義文件

inline void A::Foo(int x, int y){}

慎用內聯

內聯能提高函數的執行效率,為什么不把所有的函數都定義成內聯函數?如果所有的函數都是內聯函數,還用得着“內聯”這個關鍵字嗎?內聯是以代碼膨脹(復制)為代價,僅僅省去了函數調用的開銷,從而提高函數的執行效率如果執行函數體內代碼的時間,相比於函數調用的開銷較大,那么效率的收獲會很少。另一方面,每一處內聯函數的調用都要復制代碼,將使程序的總代碼量增大,消耗更多的內存空間。

以下情況不宜使用內聯:

(1)如果函數體內的代碼比較長,使用內聯將導致內存消耗代價較高。

(2)如果函數體內出現循環,那么執行函數體內代碼的時間要比函數調用的開銷大。類的構造函數和析構函數容易讓人誤解成使用內聯更有效。要當心構造函數和析構函數可能會隱藏一些行為,如“偷偷地”執行了基類或成員對象的構造函數和析構函數。所以不要隨便地將構造函數和析構函數的定義體放在類聲明中。一個好的編譯器將會根據函數的定義體,自動地取消不值得的內聯(這進一步說明了 inline 不應該出現在函數的聲明中)。


注意點:


內聯函數既能夠去除函數調用所帶來的效率負擔又能夠保留一般函數的優點。然而,內聯函數並不是萬能葯,在一些情況下,它甚至能夠降低程序的性能。因此在使用的時候應該慎重。 
1.我們先來看看內聯函數給我們帶來的好處:從一個用戶的角度來看,內聯函數看起來和普通函數一樣, 它可以有參數和返回值,也可以有自己的作用域,然而它卻不會引入一般函數調用所帶來的負擔。另外, 它可以比宏更安全更容易調試。 
當然有一點應該意識到,inline specifier僅僅是對編譯器的建議,編譯器有權利忽略這個建議。那么編譯器是如何決定函數內聯與否呢?一般情況下關鍵性因素包括函數體的大小,是否有局部對象被聲明,函數的復雜性等等。 
2.那么如果一個函數被聲明為inline但是卻沒有被內聯將會發生什么呢?理論上,當編譯器拒絕內聯一個 函數的時候,那個函數會像普通函數一樣被對待,但是還會出現一些其他的問題。例如下面這段代碼: 

[cpp]  view plain  copy
 
  1. // filename Time.h   
  2. #include<ctime>   
  3. #include<iostream>   
  4. using namespace std;   
  5. class Time   
  6. {   
  7. public:   
  8. inline void Show()   
  9.   
  10. {   
  11. for (int i = 0; i<10; i++)  
  12. cout<<time(0)<<endl;  
  13. }   
  14. };   



因為成員函數Time::Show()包括一個局部變量和一個for循環,所以編譯器一般拒絕inline,並且把它當作一個普通的成員函數。但是這個包含類聲明的頭文件會被單獨的#include進各個獨立的編譯單元中: 

[cpp]  view plain  copy
 
  1. // filename f1.cpp   
  2. #include "Time.h"   
  3. void f1()   
  4. {   
  5. Time t1;   
  6. t1.Show();   
  7. }   
  8. // filename f2.cpp   
  9. #include "Time.h"   
  10. void f2()   
  11. {   
  12. Time t2;   
  13. t2.Show();   
  14. }   


結果編譯器為這個程序生成了兩個相同成員函數的拷貝: 

[cpp]  view plain  copy
 
  1. void f1();   
  2. void f2();   
  3. int main()   
  4. {   
  5. f1();   
  6. f2();   
  7. return 0;   
  8. }   



當程序被鏈接的時候,linker將會面對兩個相同的Time::Show()拷貝,於是函數重定義的連接錯誤發生。但是老一些的C++實現對付這種情況的辦法是通過把一個un-inlined函數當作static來處理。因此每一份函數拷貝僅僅在自己的編譯單元中可見,這樣鏈接錯誤就解決了,但是在程序中卻會留下多份函數拷貝。在這種情況下,程序的性能不但沒有提升,反而增加了編譯和鏈接時間以及最終可執行體的大小。但是幸運的是,新的C++標准中關於un-inlined函數的說法已經改變。一個符合標准C++實現應該只生成一份函數拷貝。然而,要想所有的編譯器都支持這一點可能還需要很長時間。


另外關於內聯函數還有兩個更令人頭疼的問題。第一個問題是該如何進行維護。一個函數開始的時候可能以內聯的形式出現,但是隨着系統的擴展,函數體可能要求添加額外的功能,結果內聯函數就變得不太可能,因此需要把inline specifier去除以及把函數體放到一個單獨的源文件中。另一個問題是當內聯函數被應用在代碼庫的時候產生。當內聯函數改變的時候,用戶必須重新編譯他們的代碼以反映這種改變。然而對於一個非內聯函數,用戶僅僅需要重新鏈接就可以了。


這里想要說的是,內聯函數並不是一個增強性能的靈丹妙葯。只有當函數非常短小的時候它才能得到我們想要的效果,但是如果函數並不是很短而且在很多地方都被調用的話,那么將會使得可執行體的體積增大。最令人煩惱的還是當編譯器拒絕內聯的時候。在老的實現中,結果很不盡人意,雖然在新的實現中有很大的改善,但是仍然還是不那么完善的。一些編譯器能夠足夠的聰明來指出哪些函數可以內聯哪些不能,但是,大多數編譯器就不那么聰明了,因此這就需要我們的經驗來判斷。如果內聯函數不能增強行能,就避免使用它!


免責聲明!

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



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