C++中inline用法詳解

一、爲什麼使用inline函數?[1]

1.1爲了解決一些頻繁調用的小函數大量消耗棧空間(棧內存)。

在預編譯的時候,編譯器將程序中出現的內聯函數的調用表達式的地方直接插入用內聯函數的代碼。

1.2用它替代C中表達式形式的宏定義(macros)。

對於形似函數的宏,最好改用inline函數替換#define。
//標準的max template(來自<algorithm>)往往是這樣實現出來的:
template <typename T>
inline const T& std::max(const T& a,const T& b)
{return a<b?b:a}

二、inline的使用與限制

2.1如果想把一個函數定義爲內聯函數,則需要在函數名前面放置關鍵字 inline,內聯函數的定義必須出現在內聯函數第一次調用之前。
2.2關鍵字 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 與函數定義體放在一起
tips:儘管在大多數教科書中內聯函數的聲明、定義體前面都加了inline 關鍵字,但我認爲inline不應該出現在函數的聲明中。這個細節雖然不會影響函數的功能,但是體現了高質量C++/C 程序設計風格的一個基本原則:聲明與定義不可混爲一談,用戶沒有必要、也不應該知道函數是否需要內聯。
2.3建議 inline 函數的定義放在頭文件中。[2]
    Inline函數通常一定被置於頭文件內,因爲大多數建置環境在編譯過程中進行內聯,而爲了將一個“函數調用”替換爲“被調用函數的本體”,編譯器必須知道那個函數長什麼樣子。某些建置環境可以在連接器完成內聯,少數建置環境竟可以在運行期完成內聯。然而這樣的環境畢竟是例外,不是通利。
    聲明跟定義要一致:如果在每個文件裏都實現一次該內聯函數的話,那麼,最好保證每個定義都是一樣的,否則,將會引起未定義的行爲。如果不是每個文件裏的定義都一樣,那麼,編譯器展開的是哪一個,那要看具體的編譯器而定。所以,最好將內聯函數定義放在頭文件中。
2.4在內聯函數內不允許使用循環語句和開關語句。
    內聯那些包含循環或 switch 語句的函數常常是得不償失 (除非在大多數情況下, 這些循環或 switch 語句從不被執行).
有些函數即使聲明爲內聯的也不一定會被編譯器內聯, 這點很重要; 比如虛函數和遞歸函數就不會被正常內聯. 通常, 遞歸函數不應該聲明成內聯函數.(遞歸調用堆棧的展開並不像循環那麼簡單, 比如遞歸層數在編譯時可能是未知的, 大多數編譯器都不支持內聯遞歸函數). 虛函數內聯的主要原因則是想把它的函數體放在類定義內, 爲了圖個方便, 抑或是當作文檔描述其行爲, 比如精短的存取函數。

三、inline僅是一個對編譯器的建議

inline 函數僅僅是一個對編譯器的建議而已,並不是說聲明瞭內聯就會內聯。所以最後能否真正內聯,看編譯器的意思,它如果認爲函數不復雜,能在調用點展開,纔會真正內聯。因爲,編譯器比程序設計者更清楚對於一個特定的函數是否合適進行內聯擴展;一些情況下,對於程序員指定的某些內聯函數,編譯器可能更傾向於不使用內聯甚至根本無法完成內聯。

四、C++ 內聯函數是通常與類一起使用。

在類定義中的定義的函數(類內部)都是內聯函數,即使沒有使用 inline 說明符。如果在類中未給出成員函數定義,而又想內聯該函數的話,那在類外要加上 inline,否則就認爲不是內聯的。值得注意的是:如果在類體外定義inline函數,則心須將類定義和成員函數的定義都放在同一個頭文件中,否則編譯時無法進行置換。

五、慎用 inline

    如果執行函數體內代碼的時間,相比於函數調用的開銷較大,那麼效率的收穫會很少。另一方面,每一處內聯函數的調用都要複製代碼,將使程序的總代碼量增大,消耗更多的內存空間。
要當心構造函數和析構函數可能會隱藏一些行爲,如“偷偷地”執行了基類或成員對象的構造函數和析構函數。如果將類的析構函數定義爲內聯函數,可能會導致潛在的代碼膨脹。

[1]inline與#define的區別
[2]頭文件怎麼寫?

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