一、c++ inline函數產生原因
由於函數調用會有一定的時間和空間方面的開銷,特別是對於一些函數體代碼不大但又被頻繁調用的函數來講,效率是很低的。
在C語言中,可以用宏函數來提高上面那種情況的效率,但宏函數有些缺點,它只是提供一個文本替換的功能,而不是一個真正的函數。所以在C++中引入了inline函數來解決這個問題,它會像宏函數一樣在調用函數處用內聯函數體的代碼進行替換,還遵循函數的類型和作用域規則,所以它能像一般的函數那樣進行調用和調試。
二、如何定義inline函數
inline函數只有和函數體定義放在一起是纔有效,所以如果像這樣聲明一個內聯函數
- inline void Func();
那麼在編譯器看來,它與普通函數沒有兩樣,如下所示
- inline void Func()
- {
- // ...
- }
這樣它才具有比一般函數更快的執行能力。
還有一種定義方式是在類內部定義的函數。在c++中,如果在類內部定義了函數體的函數,則默認其爲內聯,而不管是否有inline關鍵字。所以像如下兩個函數Func1、Func2都屬於內聯。
- class A
- {
- public:
- inline void Fun1(){ //... }
- void Fun2(){ //... }
- }
當然,內聯函數並不是萬能的,在某些情況下,它不僅不能像期望的那樣提升性能,甚至會起反作用。inline只是對編譯器的一種提示,而不是命令。也就是說,只要編譯器願意,它就可以隨意地忽略掉你的指令,當編譯器遇到內聯函數時,就會針對函數體的上下文進行優化,以確定是否執行內聯。如果編譯器認爲當前的函數過於複雜、函數體過大,或者這個函數是虛函數,就會拒絕將其內聯,這取決與編譯器,不同編譯器處理方式不一樣。
三、在使用inline時,需注意如下幾點:
1、在inline函數內不允許用循環語句和開關語句,函數體不能過於複雜;
2、對於內存空間有限的機器而言,慎用內聯。過分使用內聯會造成函數代碼過於膨脹,會佔用太多空間;
3、不能對構造或者析構函數進行內聯,儘管它們看似很簡短。對於這一點看下面這個類Derived的構造函數:
- class Base
- {
- public:
- ...
- private:
- string s1, s2;
- };
- class Derived: public Base
- {
- public:
- Derived(){}
- ...
- private s3, s4, s5;
- };
這個構造函數看起來的確是個內聯的好材料,因爲它沒有代碼,實際上,它含有相當多的代碼。對於上面這個空的Derived的構造函數,有些編譯器會爲它產生相當於下面的代碼:
- // 一個Derived構造函數的可能的實現
- Derived::Derived()
- {
- // 如果在堆上創建對象,爲其分配堆內存
- if (本對象在堆上)
- {
- this = ::operator new(sizeof(Derived));
- }
- // 初始化父類對象
- Base::Base();
- s3.string(); // 構造s3
- s4.string(); // 構造s4
- s5.string(); // 構造s5
- }
如果string的構造函數也恰巧被內聯,Derived的構造函數將得到其代碼的5個拷貝(2個繼承而來,3個自己聲明)。現在你應該明白,內聯Derived的構造函數並非可以很簡單就決定的;類似的情況也適用於析構。
4、內聯函數只適合於只有1~5行的小函數。
四、內聯還得面臨2個頭疼的問題
第一,該如何維護?一個函數開始的時候可能滿足了設定的內聯標準,並以內聯的形式出現,但隨着系統的擴展升級,函數體中增添了一些新功能,函數體變得複雜了,這就使內聯函數不再適合內聯,此時需要把函數前面的inline去掉,並把函數體放到相應的源文件中,這會使維護的難度有所增加。
第二,不可避免的重新編譯。當內聯函數實現改變的時候,用戶必須重新編譯他們的代碼以反映出這種改變。而對於非內聯函數,我們僅僅需要重新鏈接。
所以,使用inline需要依靠積累的經驗來進行判斷,合理正確的使用內聯,可以提高程序的性能,反之,會帶來意想不到的副作用!