C++中內聯函數(inline)詳解

一、?

   是指那些定義在類體內的成員函數,也就該函數的函數體放在類內。

二、爲什麼要使用inline函數

   首先引入關於調用函數的缺點:函數調用前要先保存寄存器,並在返回時恢復;複製實參;程序還必須轉向一個新位置執行。

    將一個函數聲明爲inline,那麼函數就成爲內聯函數。內聯函數通常就是它在程序中每個調用點上“內聯地”展開。從定義上看,內聯函數跟一般函數不一樣,一般函數調用的時候是需要調用開銷的(比如出棧入棧等操作),內聯函數從定義上看更像是宏,但是跟宏不一樣。

內聯函數的作用主要就是使用在一些短小而使用非常頻繁的函數中,爲了減少函數調用的開銷,爲了避免使用宏(在c++中,宏是不建議使用的)。比如內聯函數inline int  func(int x){return x*x;} 在調用的時候cout<<func(x)<<endl,在編譯時將被展開爲:

cout<<(x*x)<<endl;

三、內聯函數相對於宏的區別和優點

             從上面的分析中,可以看出,內聯函數在表現形式上與宏很類似。但是內聯函數和宏之間的區別很明顯。宏是在預處理時進行的機械替換,內聯是在編譯時進行的。內聯函數是真正的函數,只是在調用時,沒有調用開銷,像宏一樣進行展開。內聯函數會進行參數匹配檢查,相對於帶參數的宏有很好的優點,避免了處理宏的一些問題。

四、內聯函數相對於宏的區別和優點

          要讓一個函數稱爲內聯函數,有兩種方法:一種是把函數加上inline關鍵字;一種是在類的說明部分定義的函數,默認就是內聯的。

要禁止編譯器進行內聯,可以使用#pragma auto_inline編譯指令或者改變編譯參數。

五、內聯函數的實現

  下面引自林銳博士的《高質量C++編程》P67

 定義在類聲明之中的成員函數將自動地成爲內聯函數,例如
          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)
               {

                  }


C++的發明者Bjarne Stroustrup博士在他所著的書《C++語言的設計和演化》中則是這樣論述這個問題(見《C++語言的設計和演化》P12):
在帶類的C中只有成員函數能做成在線(注:也就是設計爲inline函數)的,而要求函數成爲在線只有一種方式,那就是把它的放進類的聲明之中。例如:
                class  stack
               {
                            /* … /*
                    char pop()
                     {
                          If(top<=min) error(“stack underflow”);
                                return *--top;
                     }
             };
事實上,那時也看到這會使類的聲明顯得比較雜亂。另一方面,這看起來也是個好東西,因爲它不鼓勵在線函數的過度使用。關鍵字inline和允許在線成員函數的功能都是後來由C++提供的。例如,在C++中可以寫下面這樣的代碼:
                   class  stack
                      {
                            /* … /*
                     char pop();
                     };

                     Inline char stack:: pop()
                   {
                        If(top<=min) error(“stack underflow”);
                          return *--top;
                   }

  所以這就與下面的第(3)點相對應

六、內聯函數注意事項

  (1)       內聯函數一定會內聯展開嗎?答案是否定的。對於內聯函數,程序只是提供了一個“內聯建議”,即建議編譯器把函數用內聯展開,但是真正是否內聯,是由編譯器決定的,對於函數體過大的函數,編譯器一般不會內聯,即使制定爲內聯函數。

(2)       在內聯函數內部,不允許用循環語句和開關語句(if或switch)。內聯函數內部有循環和開關,也不會出錯,但是編譯器會把它當做非內聯函數的。

(3)       關鍵字inline必須與函數定義體放在一起才能使函數真正內聯,僅把inline放在函數聲明的前面不起任何作用。因爲inline是一種用於實現的關鍵字,不是一種用於聲明的關鍵字。內聯函數的聲明是不需要加inline關鍵字的,內聯函數的定義是必須加inline的(除了類的定義部分的默認內聯函數),儘管很多書聲明定義都加了,要注意理解聲明和定義的區別。

      但是問題來了:(內聯函數是放在.h還是.cpp中)

               參考大神的說法:首先介紹一個概念,“編譯單元”,用不太嚴謹的方式定義,就是當你把一個源文件(.cpp .cxx等)做完預處理,也就是把包含的頭文件的內容全部放到這個文件裏來,所有宏都展開,等等,形成的一個邏輯上的實體——就是編譯單元一個編譯單元可以單獨編譯,但不能鏈接成一個可執行程序(除非程序只有這個編譯單元);         有了編譯單元的概念以後,你只要確保以下這個原則就可以了:如果在這個編譯單元裏使用了一個Inline函數,那麼我在這個編譯單元結束之前,
必須能夠“看到”這個編譯單元的完整定義(所有實現代碼)                 另外要注意,在編譯單元之內,調用inline函數的代碼行之前,至少要放置
一個這個inline函數的聲明,當然有定義也可以
             從這個原則出發,最簡單的使用Inline函數的方法就是在頭文件定義,
            否則你要在每一個使用inline函數的編譯單元裏一一定義這個函數,
如果有n個編譯單元,你就要把inline函數的代碼重複書寫n次


            參考C++primer:內聯函數應該在頭文件中定義;在頭文件中加入或修改內聯函數時,使用了該頭文件的所有源文件都必須重新編譯。

(4)       在一個文件中定義的內聯函數不能在另一個文件中使用。它們通常放在頭文件中共享。

(5)       內聯函數的定義必須在第一次調用之前。注意,這裏是定義之前,不僅僅是聲明之前。對於普通函數,可以在調用之前聲明,調用代碼之後具體定義(實現函數),但是內聯函數要實現內聯,必須先定義再調用,否則編譯器會把在定義之前調用的內聯函數當做普通函數進行調用。

(6)       說明:上面這些inline的注意事項,在編程時要自己注意,因爲上面的注意事項不遵守很多並不會引起編譯錯誤,只是會導致寫了inline的函數不是內聯函數,從而與預期的目的不一樣。所以很多沒法用程序實例說明到底編譯器是按照inline還是非inline調用的,或許分析彙編代碼能看出,但是水平有限,就不多分析了。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章