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 模板(來自 <algorithm>)經常用到的的實現方法:
template<typename T> // 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 void f() {...} // assume compilers are willing to inline calls to f
void (*pf)() = f; // pf points to f ...
f(); // this call will be inlined, because it’s a "normal" call pf(); // this call probably won’t be, because it’s through // a function pointer 甚至在你從來沒有使用函數指針的時候,未 inline 化的 inline 函數的幽靈也會時不時地拜訪你,因為程序員并不必然是函數指針的唯一需求者。有時候編譯器會生成構造函數和析構函數的 out-of-line 拷貝,以便它們能得到指向這些函數的指針,在對數組中的對象進行構造和析構時使用。
C++ 為對象被創建和被銷毀時所發生的事情做出了各種保證。例如,當你使用 new 時,你的動態的被創建對象會被它們的構造函數自動初始化,而當你使用 delete。則相應的析構函數會被調用。當你創建一個對象時,這個對象的每一個基類和每一個數據成員都會自動構造,而當一個對象被銷毀時,則發生關于析構的反向過程。假如在一個對象構造期間有一個異常被拋出,這個對象已經完成構造的任何部分都被自動銷毀。所有這些情節,C++ 只說什么必須發生,但沒有說如何發生。那是編譯器的實現者的事,但顯然這些事情不會自己發生。在你的程序中必須有一些代碼使這些事發生,而這些代碼——由編譯器寫出的代碼和在編譯期間插入你的程序的代碼——必須位于某處。有時它們最終就位于構造函數和析構函數中,所以我們可以設想實現為上面那個聲稱為空的 Derived 的構造函數生成的代碼就相當于下面這樣:
Derived::Derived() // conceptual implementation of { // "empty" Derived ctor
Base::Base(); // initialize Base part
try { dm1.std::string::string(); } // try to construct dm1 catch (...) { // if it throws, Base::~Base(); // destroy base class part and throw; // propagate the exception }
try { dm2.std::string::string(); } // try to construct dm2 catch(...) { // if it throws, dm1.std::string::~string(); // destroy dm1, Base::~Base(); // destroy base class part, and throw; // propagate the exception }
try { dm3.std::string::string(); } // construct dm3 catch(...) { // if it throws, dm2.std::string::~string(); // destroy dm2, dm1.std::string::~string(); // destroy dm1, Base::~Base(); // destroy base class part, and throw; // propagate the exception } } 這些代碼并不代表真正的編譯器所生成的,因為真正的編譯器會用更復雜的方法處理異常。盡管如此,它還是準確地反映了 Derived 的“空”構造函數必須提供的行為。不論一個編譯器的異常多么復雜,Derived 的構造函數至少必須調用它的數據成員和基類的構造函數,而這些調用(它們自己也可能是 inline 的)會影響它對于 inline 化的吸引力。
庫設計者必須評估聲明函數為 inline 的影響,因為為庫中的客戶可見的 inline 函數提供二進制升級版本是不可能的。換句話說,假如 f 是一個庫中的一個 inline 函數,庫的客戶將函數 f 的本體編譯到他們的應用程序中。假如一個庫的實現者后來決定修改 f,所有使用了 f 的客戶都必須重新編譯。這經常會令人厭煩。在另一方面,假如 f 是一個非 inline 函數,對 f 的改變只需要客戶重新連接。這與重新編譯相比顯然減輕了很大的負擔,而且,假如庫中包含的函數是動態鏈接的,這就是一種對于用戶來說完全透明的方法。