member functions的調用方式 c++支持三種類型的member functions:static、nonstatic、virtual,且每一種調用方式不盡相同 nonstatic member functions nonstatic member function至少和nonmembe ...
member functions的調用方式
- c++支持三種類型的member functions:static、nonstatic、virtual,且每一種調用方式不盡相同
nonstatic member functions
- nonstatic member function至少和nonmember function有著相同的效率
現有如下函數調用:
float do(const A *_this) {...}
float A::do() const {...}
//第一個函數轉換
float do(const A *_this)
{
return sqrt(
_this->x * _this->x + _this->y * _this->y + _this->z * _this->z
)
};
//第二個nonstatic member function轉換成上面相同的形式nonmember function
-
nonstatic member function轉化為 nonmember function步驟:
-
改寫函數原型以安插一個額外參數到member function,將提供class object存取渠道,此參數也就是this指針。若member function為const,this也需加個const
float do(const A* const _this)
-
將每個對nonstatic data member的存取操作改為this指針
_this->x * _this->x + _this->y * _this->y + _this->z * _this->z
-
將member function經過mangling處理重新寫為一個外部函數,且名稱是獨一無二的
extern do_7AFv( A* const this );
-
-
一般而言,data member的名稱前會加上class名稱,形成獨一無二的命名;而member function則還需加上參數鏈表
class B { public: int val; ... }; //對val進行name mangling val_3; ----------------------- class C { public: void x(float newX); float x(); } //member function進行name mangling void x__5CFf(float newX); float x__5CFv();
virtual member functions
現有如下代碼:
class A
{
virtual A do1() const;
virtual float do2() const;
}
A a;
A* pt = &a;
pt->do1();
//轉化
( *pt->vptr[1] )(pt);
float d = do2();
//轉化
float d = ( *this->vptr[2] )( this );
a.do1();
//轉化
( *a.vptr[1] )( &a );
( * pt -> vptr [1] )( pt )其中:1為virtual table slot的索引值,關聯到virtual member function,也就是do1()
( *a.vptr[1] )( &a ) 沒有必要,應該這樣調用:A::do1()
-
經由class object調用virtual function優化跟nonstatic member function一樣
-
為支持virtual function機制,需要能對多態對象進行執行期類型判斷,將必要信息加在指針或引用上。而必要信息則有:
- 指針或引用指向的對象的地址
- 對象類型的結構的地址
-
多態表示用一個public base class的指針或引用定址一個derived class object
Point* ptr; ptr = new Point2d;
-
當前ptr被稱為消極的多態形式,可以在編譯時期完成(virtual base class除外);當ptr指出的具體對象被使用時才是積極的
ptr->z();
-
class是否支持多態,唯一方法是看其是否有virtual function
-
-
實現多態。我們需要在每個class object上增加兩個members:
- 一個字元串或數字,表示class類型
- 一個指針,指向一表格,表格中含有virtual functions執行期地址
-
隨後只需兩步即可找到其地址:
- 每個class object安插一個由編譯器生成的指針,該指針指向表格
- 每個virtual function被指派一個表格索引值
-
一個class只有一個virtual table,每個table內含對應的class object中的active virtual functions實例地址。而active virtual functions又包括:
- 這一class定義的函數實例
- 繼承自base class的函數實例
- 一個pure virtual called函數
-
對於多重繼承,銷毀對象時若調用delete需要指向derived class object的起始處,但因為指針或引用真正所指的對象在執行期才可以確定,所以offset無法在編譯期求得。針對多重繼承這種情況,derived class需要內含n-1個額外的virtual tables(n表示其上一層base classes個數)。那麼如何支持一個class擁有多個virtual tables呢?只需將每一個tables以外部對象的形式產出,並賦予獨一無二的名稱
-
不要在virtual base class中聲明nonstatic data members
static member function
- static member function特性:
- 沒有this指針,成為一個callback函數
- 不可直接存取class中的nonstatic members
- 不可被聲明為const、volatile、virtual
- 不需要經由class object調用
- static member function同樣也有name mangling
- 對static member function取地址,得到的是記憶體中的地址;由於沒有this指針,static member function地址類型是一個nonmember函數指針
指向member functions 的指針
-
指向member function的指針和指向member selection operator的指針,其作用是作為this指針的空間保留者。這也說明瞭為什麼static member function的指針類型是函數指針,畢竟其沒有this指針
-
使用member function指針,若不用於virtual function、多重virtual繼承、virtual base class,其成本跟用nomember function指針差不多
-
virtual member function的地址在編譯期是未知的,我們所能知道的僅是virtual function在其相關之virtual table的索引值
inline functions
-
inline關鍵詞只是一個請求,若此請求被編譯期接受,編譯期則認為其可以用一個表達式將函數展開
-
inline函數的複雜度通過計算assignments、function calls、virtual function calls等操作的次數以及每個表達式種類的權值綜合決定
-
若函數因其複雜度或建構問題,被判斷不可稱為Inline,那麼此函數將被轉換為static函數,併在"被編譯模塊"內產生對應的函數定義
-
inline function展開期間,做了以下三件事:
-
每個形參都被對應的實參取代。但這其中可能會導致實際參數的多次求值,面對這種情況,需要引入臨時對象。比如,若實際參數是常量表達式,在替換前先引入臨時對象,常量表達式求值後賦值給臨時對象,後繼inline替換隻需使用臨時對象
inline int min( int i, int j ) { return i < j ? i : j; } inline int bar() { int minval; minval = min( foo(), bar() + 1 ); return minval; } //minval = min( foo(), bar() + 1 )展開 int t1, t2; minval = ( t1 = foo() ), ( t2 = bar() + 1 ), t1 < t2 ? t1 : t2;
-
若內含局部變數,則需將局部變數放在函數調用的一個封閉區段,且擁有一個獨一無二的名稱。因為,如果Inline以單一表達式的方式擴展多次,每次擴展都需要自己的局部變數,特別是還含有副作用參數,可能會導致大量臨時性對象產生;但如果是分離成多個式子擴展多次,只需一組局部變數即可重覆使用
inline int min( int i, int j ) { int minval = i < j ? i : j; //局部變數 return minval; } { ... //minval = min(val1, val2); int __min_lv_minval; minval = (__min_lv_minval = val1 < val2 ? val1 : val2), __min_lv_minval; }
-
inline函數提供強有力的工具,但於noninline函數相比還是需要更小心地處理
-
-
儘量不要Inline中套inline,可能會使簡單的Inline因其連鎖複雜度而沒辦法展開