1. 程式人生 > >深入探索C++物件模型(六) Function語義學

深入探索C++物件模型(六) Function語義學

1. 非虛成員函式指標(非靜態)。

取一個非靜態成員函式的地址,如果該函式是非虛擬函式,則得到的是它在記憶體中的真正地址,但是該地址並不完全,需要繫結與具體的類例項(物件)之上,藉助物件的地址(this指標)才可以被呼叫,例如:一個成員函式指標

  1. double (Point::* pmf)();  
經過初始化:
  1. double (Point::*coord)() = &Point::getX;//或者這樣初始化它:coord = &Point::getX;
這樣來呼叫它:
  1. (orgin.*coord)();//或者這樣(ptr->*coord)();
呼叫會轉化成:
  1. (coord)(&origin);
    //或者(coord)(ptr);
靜態成員函式由於沒有this指標,所以指向它的指標是函式指標,而不是指向成員函式的指標。

2. 指向虛成員函式的指標。

當一個函式指標指向一個虛成員函式時,例如:

  1. float (Point::*pmvf)() = &Point::z;//z為虛擬函式
  2. Point *ptr = new Point3d;//Point3d為Point的子類
那麼,當ptr通過該函式指標呼叫z時,多型機制仍會生效,也就是如下呼叫,呼叫的仍是Point3d的z()函式。
  1. (ptr->*pmvf)();//效果等同於ptr->z();
這是因為,取去函式的地址,,得到的是其在虛表中的索引值,也就是對於如下類:
  1. class Point {  
  2. public:  
  3.     virtual ~ Point();  
  4.     float x();  
  5.     float y();  
  6.     virtualfloat z();  
  7. };  
&Point::~Point得到的結果是1,&Point::x和&Point::y得到的是它們在記憶體中的地址,因為它們並非虛擬函式,而&Point::z結果為2,因為它位於虛表的第三個位置(索引從0開始),所以通過上面例子中的pmvf呼叫函式,會轉化為:
  1. (*ptr->vptr[(int)pvmf])(ptr);//呼叫Point3d::z()
3. 多重繼承下,指向成員函式的指標。

由於多重繼承(包括多重虛擬繼承)涉及到,子類中可能存在多個虛表,this指標的可能需要調整偏移,書中舉例了cfront的實現方法,引入一個結構體,在需要的時候儲存上述內容,結構體如下:

  1. struct __mptr {  
  2.     int delta;//虛基類或者多重繼承下的第二個以及之後基類的this指標偏移
  3.     int index;//虛擬函式索引,非虛擬函式此值為-1
  4.     union {  
  5.         ptrtofunc faddr;//非虛擬函式地址
  6.         int v_offset;//虛基類或者多重繼承下的第二個以及之後基類的虛表位置
  7.     };  
  8. };  
在此模型下,以下呼叫:
  1. (ptr->*pmvf)();  
會變成:
  1. (pmvf.index < 0) ?  
  2. (*pmvf.faddr)(ptr)//非虛擬函式
  3. : (*ptr->vptr[pmvf.index])(ptr);//虛擬函式

1. 對於單一表達式的多重呼叫:

對如下incline函式:

  1. inline Point operator+ (const Point& lhs, const Point& rhs)  
  2. {  
  3.     Point new_pt;  
  4.     new_pt.x(lhs.x() + rhs.x());//x()函式為成員變數_x的get,set函式
  5.     return new_pt;  
  6. }  
由於該函式只是一個表示式,在cfront中,則其第二或者後繼的呼叫操作不會被擴充套件,會變成:
  1. new_pt.x = lhs._x + x__5PointFV(&rhs);  
沒有帶來效率上的提升,需要重寫為:
  1. new_pt.x(lhs._x + rhs._x);  
2. inline函式對於形式(Formal)引數的處理。

有如下inline函式:

  1. inlineint min(int i, int j)  
  2. {  
  3.     return i < j ? i : j;  
  4. }  
如下三個呼叫:
  1. inlineint bar()  
  2. {  
  3.     int minVal;  
  4.     int val1 = 1024;  
  5.     int val2 = 2048;  
  6.     minVal = min(val1, val2);//case 1
  7.     minVal = min(1024, 2048);//case 2
  8.     minVal = min(foo(), bar() + 1);//case 3
  9.     return minVal;  
  10. }  
對於case1,呼叫會直接展開:
  1. minVal = val1 < val2 ? val1 : val2;  
case2直接使用常量:
  1. minVal = 1024;  
對於case3,由於引發了引數的副作用,需要匯入臨時物件,以避免重複求值(注意下面逗號表示式的使用):
  1. int t1;  
  2. int t2;  
  3. minVal = (t1 = foo()), (t2 = bar() + 1), t1 < t2 ? t1: t2;  
3. inline函式中引入了局部變數。
改寫上述min函式,引入一個區域性變數:
  1. inlineint min(int i, int j)  
  2. {  
  3.     int minVal = i < j ? i : j;  
  4.     return minVal;  
  5. }  
對於如下呼叫:
  1. int minVal;  
  2. int val1 = 1024;  
  3. int val2 = 2048;  
  4. minVal = min(val1, val2);  
為了維護區域性變數,會被擴充套件為:
  1. int minVal;  
  2. int val1 = 1024;  
  3. int val2 = 2048;  
  4. int __min_lv_minVal;//將inline函式區域性變數mangling
  5. minVal = (__min_lv_minVal = val1 < val2 ? val1: val2), __min_lv_minVal;  

再複雜一些的情況,例如區域性變數加上有副作用的引數,會導致大量臨時性物件的產生:

  1. minVal = min(val1, val2) + min(foo(), bar() + 1);  

會被擴充套件成為,注意逗號表示式,由左至右計算各個分式,以最右端的分式值作為最終值傳回:

  1. int __min_lv_minVal_00;  
  2. int __min_lv_minVal_01;  
  3. int t1;  
  4. int t2;  
  5. minVal = (__min_lv_minVal_00 = val1 < val2 ? val1 : val2, __min_lv_minVal_00) +  
  6. (__min_lv_minVal_01 = (t1 = foo(), t2 = bar() + 1, t1 < t2 ? t1 : t2), __min_lv_minVal_01);  
會產生多個臨時變數,所以inline函式的使用必須要謹慎。