inline 函數——多麼棒的主意啊!它們看起來像函數,它們產生的效果也像函數,它們在各方面都比宏好得太多太多,而你卻可以在調用它們時不招致函數調用的成本。你還有什麼更多的要求呢?
實際上你得到的可能比你想的更多,因為避免函數調用的成本只是故事的一部分。在典型情況下,編譯器的最佳化是為了一段連續的沒有函數調用的代碼設計的,所以當你 inline 化一個函數,你可能就使得編譯器能夠對函數體實行上下文相關的特殊最佳化。大多數編譯器都不會對 "outlined" 函數調用實行這樣的最佳化。
然而,在編程中,就像在生活中,沒有免費午餐,而 inline 函數也不例外。一個 inline 函數背後的思想是用函數本體代替每一處對這個函數的調用,而且不必拿著統計表中的 Ph.D. 就可以看出這樣可能會增加你的目標代碼的大小。在有限記憶體的機器上,過分熱衷於 inline 化會使得程式對於可用空間來說過於龐大。即使使用了虛擬記憶體,inline 引起的代碼膨脹也會導致附加的分頁調度,減少指令快取命中率,以及隨之而來的效能損失。
在另一方面,如果一個 inline 函數本體很短,為函數本體產生的程式碼可能比為一個函數調用產生的程式碼還要小。如果是這種情況,inline 化這個函數可以實際上導致更小的目標代碼和更高的指令快取命中率! 記住,inline 是向編譯器發出的一個請求,而不是一個命令。這個請求能夠以顯式的或隱式的方式提出。隱式的方法就是在一個類定義的內部定義一個函數:
class Person {
public:
...
int age() const { return theAge; } // an implicit inline request: age is
... // defined in a class definition
private:
int theAge;
};
這樣的函數通常是成員函數,不過我們知道友元函數也能被定義在類的內部,如果它們在那裡,它們也被隱式地聲明為 inline。
顯式的聲明一個 inline 函數的方法是在它的聲明之前加上 inline 關鍵字。例如,以下就是標準 max 模板(來自 )經常用到的的實現方法:
template // an explicit inline
inline const T& std::max(const T& a, const T& b) // request: std::max is
{ return a < b ? b : a; } // preceded by "inline"
max 是一個模板的事實引出一個觀察結論:inline 函數和模板一般都是定義在標頭檔中的。這就使得一些程式員得出結論斷定函數模板必須是 inline。這個結論是非法的而且有潛在的危害,所以它值得我們考察一下。 inline 函數一般必須在標頭檔內,因為大多數構建環境在編譯期間進行 inline 化。為了用被調用函數的函數本體替換一個函數調用,編譯器必須知道函數看起來像什麼樣子。(有一些構建環境可以在串連期間進行 inline 化,還有少數幾個——比如,基於 .NET Common Language Infrastructure (CLI) 的控制環境——居然能在運行時 inline 化。然而,這些環境都是例外,並非規則。inline 化在大多數 C++ 程式中是一個編譯時間行為。)
模板一般在標頭檔內,因為編譯器需要知道一個模板看起來像什麼以便用到它時對它進行執行個體化。(同樣,也不是全部如此。一些構建環境可以在串連期間進行模板執行個體化。然而,編譯期執行個體化更為普遍。) 模板執行個體化與 inline 化無關。如果你寫了一個模板,而且你認為所有從這個模板執行個體化出來的函數都應該是 inline 的,那麼就聲明這個模板為 inline,這就是上面的 std::max 的實現被做的事情。但是如果你為沒有理由要 inline 化的函數寫了一個模板,就要避免聲明這個模板為 inline(無論顯式的還是隱式的)。inline 化是有成本的,而且你不希望在毫無預見的情況下遭遇它們。我們已經說到 inline 化是如何引起代碼膨脹的,但是,還有其它的成本,過一會兒我們再討論。