1. 程式人生 > >C++對象模型——Inline Functions(第四章)

C++對象模型——Inline Functions(第四章)

優化 tor tracking 改善 pan c++ col ria 表達式

4.5 Inline Functions

以下是Point class 的一個加法運算符的可能實現內容:
class Point {
	friend Point operator+(const Point&, const Point&);
};
Point operator+(const Point &lhs, const Point &rhs) {
	Point new_pt;
	new_pt._x = lhs._x + rhs._x;
	new_pt._y = lhs._y + rhs._y;
	return new_pt;
}
理論上,一個比較"幹凈"的做法是使用 inline 函數set和get函數來完畢.
// void Point::x(float new_x) { _x = new_x; }
// float Point::x() { return _x; }
new_pt.x(lhs.x() + rhs.x());
因為受限僅僅能在上述兩個函數中對_x直接存取,因此也就將稍後可能發生的data members的改變(比如在繼承體系中上移或下移)所帶來的沖擊最小化了.假設把這些存取函數聲明為 inline,就能夠繼續保持直接存取members的那種高效率--同一時候我們也兼顧了函數的封裝性.此外,加法運算符不再須要被聲明為Point的一個 friend.
然而,實際上並不能強迫將不論什麽函數都變成 inline.關鍵詞 inline(或 class declaration中的member function或 friend function的定義)僅僅是一項請求.假設這項請求被接受,編譯器就必須覺得它能夠用表達式合理地將這個函數擴展開來.

編譯器相信它能夠合理地擴展一個 inline 函數,意思是在某個層次上,其運行成本比一般的函數調用以及返回機制所帶來的負荷低.cfront有一套復雜的測試法,通經常使用來計算assignments,function calls,virtual function calls等操作的次數.每一個表達式種類有一個權值,而 inline 函數的復雜度就以這些操作的總和來決定.
一般而言,處理一個 inline 函數,有兩個階段:
1.分析函數定義,以決定函數的"intrinsic inline ability"(本質的 inline 能力)."intrinsic"(本質的,固有的)一詞在這裏意指"與編譯器相關".
假設函數因其復雜度或建構問題,被推斷為不可成為 inline,它會被轉為一個 static 函數,並在"被編譯模塊"內產生相應的函數定義.
2.真正的 inline 函數擴展操作是在調用的那一點,這會帶來參數的求值操作以及暫時性對象的管理.
相同是在擴展點上,編譯器將決定這個調用是否"不可為inline".在cfront中,inline 函數假設僅僅有一個表達式,則其第二或後繼的調用操作:
new_pt.x(lhs.x() + rhs.x());
就不會被擴展開來,這是由於在cfront中它被變成:
new_pt.x = lhs._x + x_5PointFv(&rhs);
這就全然沒有帶來效率上的改善!對此,唯一可以做的就是重寫其內容:
new_pt.x(lhs._x + rhs._x);

形式參數 (Formal Arguments)

在 inline 擴展期間,究竟真正發生了什麽事情?是的,每個形式參數都會被相應的實際參數代替.假設說有什麽副作用,那就是不能夠僅僅是簡單地一一封塞程序中出現的每個形式參數,由於這將導致對於實際參數的多次求值操作.一般而言,面對"會帶來副作用的實際參數",通常都須要引入暫時性對象.換句話說,假設實際參數是一個常量表達式,能夠在替換之前先完畢求值操作;後繼的 inline 替換,就能夠把常量直接"綁"上去.假設既不是常量表達式,也不是帶有副作用的表達式,那麽就直接替換它.
舉個樣例,如果有下面簡單的 inline 函數:
inline int min(int i, int j) {
	return i < j ? i : j;
}
以下是三個調用操作:
inline int bar() {
	int minval;
	int val1 = 1024;
	int val2 = 2048;
/*1*/	minval = min(val1, val2);
/*2*/	minval = min(102, 2048);
/*3*/	minval = min(foo(), bar()+1);
	return minval;
}
標示為1的那一行會被擴展為:
// 參數直接替換
minval = val1 < val2 ? val1 : val2;
標示為2的那一行會被擴張為:
// 替換後,直接使用常量
minval = 1024;
標示為3的那一行則引發參數的副作用,它須要導入一個暫時對象,以避免反復求值:
// 有副作用,所以導入暫時對象
int t1;
int t2;
minval = (t1 = foo()), (t2 = bar() + 1), t1 < t2 ? t1 : t2;

局部變量 (Local Variables)

假設輕微地改變定義,在 inline 定義中增加一個局部變量,會如何:
inline int min(int i, int j) {
	int minval = i < j ? i : j;
	return minval;
}
這個局部變量須要什麽額外的支持或處理嗎?假設有下面的調用操作:
{
	int local_var;
	int minval;
	// ...
	minval = min(va1, val2);
}
inline 被擴展後,為了維護其局部變量,可能會變成這樣子(理論上這個樣例中的局部變量能夠被優化,其值能夠直接在minval中計算):
{
	int local_val;
	int minval;
	// 將inline函數的局部變量處以"mangling"操作
	int __min_lv_minval;
	minval = (__min_lv_minval = val1 < val2 ? val1 : val2), __min_lv_minval;
}
一般而言,inline 函數中的每個局部變量都必須放在函數調用的一個封閉區段中,擁有一個獨一無二的名稱.假設 inline 函數以單一表達式擴展多次,那麽每次擴展都須要自己的一組局部變量.假設 inline 函數以分離的多個式子被擴展多次,那麽僅僅需一組局部變量,就能夠反復使用.
inline 函數中的局部變量,再加上有副作用的參數,可能會導致大量暫時性對象的產生.特別是假設它以單一表達式被擴展多次的話.比如,以下的調用操作:
minval = min(val1, val2) + min(foo(), foo() + 1);
可能被擴展為:
// 為局部變量產生暫時變量
int __min_lv_minval_00;
int __min_lv_minval_01;
// 為放置副作用值而產生暫時變量
int t1;
int t2;
minval = ((__min_lv_minval_00 = val1 < val2 ? val1 : val2), __min_lv_minval_00) + 
	((__min_lv_minval_01 = (t1 = foo()), (t2 = foo() + 1), t1 < t2 ? t1 : t2), __min_lv_minval_01);
inline 函數對於封裝提供了一種必須的支持,可能有效存取封裝於 class 中的nonpublic數據.它同一時候也是C程序中大量使用的 #define (前置處理宏)的一個安全替代品--特別是假設宏中的參數有副作用的話,然而一個 inline 函數假設被調用太多次的話,會產生大量的擴張碼,使程序的大小暴漲.
參數帶有副作用,或是以一個單一表達式做多重調用,或是在 inline 函數中有多個局部變量,都會產生暫時性對象,編譯器或許可以把它們移除.此外,inline 中再有 inline,可能會使一個表面上看起來平庸的 inline 卻因其連鎖復雜度而沒辦法擴展開來.這樣的情況可能發生於復雜度 class 體系下的constructors,或是object體系中一些表面上並不對的 inline 調用鎖組成的串鏈--它們每個都會運行一小組運算,然後對還有一個對象發出請求.對於既要安全又要效率的程序,inline 函數提供了一個強而有力的工具.然而,與non-inline 函數比起來,它們須要更加小心地處理.

C++對象模型——Inline Functions(第四章)