1. 程式人生 > >類成員宣告與定義前加inline的區別(C++ inline 函式)

類成員宣告與定義前加inline的區別(C++ inline 函式)

轉載自:http://www.cnblogs.com/berry/articles/1582702.html

參考:http://msdn.microsoft.com/zh-cn/library/windows/apps/bw1hbe6y.aspx

心得:關鍵字inline 必須與函式定義體放在一起才能使函式成為內聯,僅將inline 放在函式宣告前面不起任何作用

(一)inline函式(摘自C++ Primer的第三版)

在函式宣告或定義中函式返回型別前加上關鍵字inline即把min()指定為內聯。

      inline int min(int first, int secend) {/****/};

      inline 函式對編譯器而言必須是可見的,以便它能夠在呼叫點內展開該函式。與非inline函式不同的是,inline函式必須在呼叫該函式的每個文字檔案中定義。當然,對於同一程式的不同檔案,如果inline函數出現的話,其定義必須相同。對於由兩個檔案compute.C和draw.C構成的程式來說,程式設計師不能定義這樣的min()函式,它在compute.C中指一件事情,而在draw.C中指另外一件事情。如果兩個定義不相同,程式將會有未定義的行為:

      為保證不會發生這樣的事情,建議把inline函式的定義放到標頭檔案中。在每個呼叫該inline函式的檔案中包含該標頭檔案。這種方法保證對每個inline函式只有一個定義,且程式設計師無需複製程式碼,並且不可能在程式的生命期中引起無意的不匹配的事情。

(二)行內函數的程式設計風格(摘自高質量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 與函式定義體放在一起{}

所以說,inline 是一種“用於實現的關鍵字”,而不是一種“用於宣告的關鍵字”。一般地,使用者可以閱讀函式的宣告,但是看不到函式的定義。儘管在大多數教科書中行內函數的宣告、定義體前面都加了inline 關鍵字,但我認為inline 不應該出現在函式的宣告中。這個細節雖然不會影響函式的功能,但是體現了高質量C++/C 程式設計風格的一個基本原則:宣告與定義不可混為一談,使用者沒有必要、也不應該知道函式是否需要內聯。

定義在類宣告之中的成員函式將自動地成為行內函數

例如

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但是卻沒有被內聯將會發生什麼呢?理論上,當編譯器拒絕內聯一個   函式的時候,那個函式會像普通函式一樣被對待,但是還會出現一些其他的問題。例如下面這段程式碼:   
  //   filename   Time.h   
  #include<ctime>   
  #include<iostream>   
  using   namespace   std;   
  class   Time   
  {   
  public:   
          inline   void   Show()  

          {   
              for (int   i   =   0;   i<10;   i++)
                  cout<<time(0)<<endl;
          }   
  };   
     因為成員函式Time::Show()包括一個區域性變數和一個for迴圈,所以編譯器一般拒絕inline,並且把它當作一個普通的成員函式。但是這個包含類宣告的標頭檔案會被單獨的#include進各個獨立的編譯單元中:   
  //   filename   f1.cpp   
  #include   "Time.h"   
  void   f1()   
  {   
          Time   t1;   
          t1.Show();   
  }   
  //   filename   f2.cpp   
  #include   "Time.h"   
  void   f2()   
  {   
          Time   t2;   
          t2.Show();   
  }   
  結果編譯器為這個程式生成了兩個相同成員函式的拷貝:   
  void   f1();   
  void   f2();   
  int   main()   
  {   
          f1();     
          f2();   
          return   0;   
  }   
     當程式被連結的時候,linker將會面對兩個相同的Time::Show()拷貝,於是函式重定義的連線錯誤發生。但是老一些的C++實現對付這種情況的辦法是通過把一個un-inlined函式當作static來處理。因此每一份函式拷貝僅僅在自己的編譯單元中可見,這樣連結錯誤就解決了,但是在程式中卻會留下多份函式拷貝。在這種情況下,程式的效能不但沒有提升,反而增加了編譯和連結時間以及最終可執行體的大小。但是幸運的是,新的C++標準中關於un-inlined函式的說法已經改變。一個符合標準C++實現應該只生成一份函式拷貝。然而,要想所有的編譯器都支援這一點可能還需要很長時間。

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

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