C++對象模型——Inline Functions(第四章)
阿新 • • 發佈:2017-06-20
優化 tor tracking 改善 pan c++ col ria 表達式
因為受限僅僅能在上述兩個函數中對_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 函數假設僅僅有一個表達式,則其第二或後繼的調用操作:
舉個樣例,如果有下面簡單的 inline 函數:
inline 函數中的局部變量,再加上有副作用的參數,可能會導致大量暫時性對象的產生.特別是假設它以單一表達式被擴展多次的話.比如,以下的調用操作:
參數帶有副作用,或是以一個單一表達式做多重調用,或是在 inline 函數中有多個局部變量,都會產生暫時性對象,編譯器或許可以把它們移除.此外,inline 中再有 inline,可能會使一個表面上看起來平庸的 inline 卻因其連鎖復雜度而沒辦法擴展開來.這樣的情況可能發生於復雜度 class 體系下的constructors,或是object體系中一些表面上並不對的 inline 調用鎖組成的串鏈--它們每個都會運行一小組運算,然後對還有一個對象發出請求.對於既要安全又要效率的程序,inline 函數提供了一個強而有力的工具.然而,與non-inline 函數比起來,它們須要更加小心地處理.
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());
然而,實際上並不能強迫將不論什麽函數都變成 inline.關鍵詞 inline(或 class declaration中的member function或 friend function的定義)僅僅是一項請求.假設這項請求被接受,編譯器就必須覺得它能夠用表達式合理地將這個函數擴展開來.
編譯器相信它能夠合理地擴展一個 inline 函數,意思是在某個層次上,其運行成本比一般的函數調用以及返回機制所帶來的負荷低.cfront有一套復雜的測試法,通經常使用來計算assignments,function calls,virtual function calls等操作的次數.每一個表達式種類有一個權值,而 inline 函數的復雜度就以這些操作的總和來決定.
一般而言,處理一個 inline 函數,有兩個階段:
1.分析函數定義,以決定函數的"intrinsic inline ability"(本質的 inline 能力)."intrinsic"(本質的,固有的)一詞在這裏意指"與編譯器相關".
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(第四章)