天天看點

C++(對象模型):08---Function之(Function Member的各種調用方式(靜态函數、非靜态函數、虛函數)、附C++的mangling機制)

前言

  • virtual函數是在20世紀80年代中期被加進來的,并且一開始受到了很多的之一
  • 靜态成員函數時最後被引入的一種函數類型。它們在1987年的Usenix C++研讨會的廠商研習營中被正式提議加入C++中

一、非靜态成員函數

  • C++的設計準則之一就是:非靜态成員函數至少必須和一般的非成員函數有相同的效率

示範說明

  • 如果我們相對類對象進行操作,那麼以下兩個函數的調用都是相同效率的,因為編譯器内部已将“成員函數實體”轉換為對等的“非成員函數實體”
float magnitude3d(const Point3d *_this) {...} //非成員函數版本
float Point3d::magnitude3d() {...}       //成員函數版本      
  • 如果是非成員函數版本:我們在函數内對類對象進行了如下操作
float magnitude3d(const Point3d *_this)
{
    return sqrt(_this->_x * _this->_x+
                _this->_y * _this->_y+
                _this->_z * _this->_z);
}      
  • 如果是成員函數版本:雖然我們沒有傳入對象指針,但是成員函數會預設包含this指針,是以成員函數可能會被編譯器進行如下的轉化
/*第一步:
  編譯器的增長過程:自動增加了this指針
  備注:如果函數為const類型,那麼參數就會變為const Point3d *const this
*/
float Point3d::magnitude3d(Point3d *const this)
{
    //通過this指針進行操作
    return sqrt(this->_x * this->_x+
                this->_y * this->_y+
                this->_z * this->_z);
}

/*第二步:
  接着将成員函數改寫為一個外部函數,并使用C++的“mangling機制”對函數的名稱進行處理,
  使函數名在程式中是獨一無二的
*/
extern magnitude_7Point3dFv(register Point3d *const this);

int main()
{
    //是以對成員函數的調用的轉換将如下所示

    Point3d obj,*ptr;

    obj.magbitude();  //将會轉換為magnitude_7Point3dFv(&obj);

    ptr->magbitude(); //将會轉換為magnitude_7Point3dFv(prt);
}      

二、名稱的特殊處理(mangling機制)

  • 規則:成員的名稱後面會被加上class名稱,形成獨一無二的名稱
  • 下面我們介紹的編碼方法是cfront采用的。目前編譯器并沒有統一的編碼方法

成員變量示範說明

  • 例如Bar類中的ival成員可能會被翻譯為ival_3Bar
class Bar { 
public:
    //被翻譯為ival_3Bar
    int ival;
 };      
  • 編譯器這麼做,是為了在派生中,子類覆寫了父類的成員,防止名稱沖突,請看下面說明
class Bar { 
public:
    int ival;
};

class Foo :public Bar {
public:
    //覆寫了父類的ival,此ival被翻譯為ival_3Foo
    int ival;
    //父類的ival在子類中為ival_3Bar,是以不會産生沖突
};      

成員函數示範說明

class Point {
public:
  void x() { float newX; }
  float x() {}
};      
  • 例如下面兩個成員函數是重載的,被編譯器翻譯為兩個不同名稱的函數
class Point {
public:
    void x_5Point() { float newX; }
    float x_5Point() {}
};      
  • 如果再把它們擦參數也加上去,就一定可以制造出獨一無二的結果了(但如果你聲明extern "C",就會壓抑非成員函數的“mangling”效果)
class Point {
public:
    void x_5PointFf() { float newX; }
    float x_5PointFv() {}
};      

三、虛拟成員函數

class Point3d {
public:
    virtual Point3d normalize()const {
        register float mag = magnitude();

        Point3d narmal;
        narmal._x = _x / mag;
        narmal._y = _y / mag;
        narmal._z = _z / mag;
        return narmal;
    }

    virtual float magnitude()const {
        return sqrt(_x*_x + _y*_y + _z*_z);
    }
protected:
    float _x,_y, _z;
};      

示範說明

  • 因為normalize()是一個虛函數,是以對其的調用,将會進行如下代碼的轉換,其中:
  • vptr:由編譯器産生的指針,指向虛函數表
  • 1:是虛函數表中的索引值,關聯到normalize()加密手機
  • 第二個ptr:表示this指針
Point3d *ptr;
ptr->normalize();

//被内部轉換為
(*ptr->vptr[1])(ptr);      
  • 類似的道理,magnitude虛函數的調用将會進行如下轉換:
class Point3d {
public:
    virtual Point3d normalize()const {
        //...

        register float mag = magnitude();
        //此語句會被翻譯為register float mag=(*this->vptr[2])(this);
        
        //...
    }

    virtual float magnitude()const {//...}
protected:
    //...
};      
  • 抑制虛拟機制:由于magnitude()是在normalize()中被調用,而後者已經由虛拟機制而決議(resolved)妥當,是以明确地調用“Point3d實體”會比較有效率,并是以壓制由于虛拟機制而産生的不必要的重複調用操作
virtual Point3d normalize()const {
    //register float mag = magnitude();
    //明确的調用操作會壓制虛拟機制
    register float mag = Point3d::magnitude();
        
    //...
}      
  • 如果magnitude()聲明為inline函數會更有效率,使用類作用域操作符明确調用一個虛函數,其決議方式會和非靜态成員函數一樣
virtual Point3d normalize()const {
    register float mag = Point3d::magnitude();
    //被翻譯為register float mag = magnitude_7Point3dFv(this);
        
    //...
}      
  • 對于調用naomalize()會被編譯器進行如下緩緩
Point3d obj;

obj.naomalize();
//會被轉換為:
(*obj.vptr[1])(&obj);      
  • 雖然語義正确,卻沒有必須。請回憶哪些并不支援多态的對象。是以上述經由obj調用的函數實體隻可以是Point::naomalize()“經由一個類對象調用虛函數”,這種操作應該總是被編譯器像對待一般的非靜态成員函數一樣地加以決議
normalize_7Point3dFv(&obj);      
  • 這項優化工程的另一個利益是,虛函數的一個内聯函數實體可以被擴充開來,因而提供極大的效率利益

四、靜态成員函數

靜态成員函數的調用轉換

  • 如果Point3d::normalize()是一個靜态函數,以下兩種調用操作将會被轉換為一般的非成員函數調用
obj.normalize();
ptr->normalize();

//以上兩種會被轉換為非成員函數的調用
normalize__7Point3dSFV();  //obj.normalize();
normalize__7Point3dSFV();  //ptr->normalize();      
  • 在C++引入靜态成員函數之前,很好看到有如下的調用方式,這種調用方式隻是簡單調用normalize這個靜态函數
((Point3d*)0)->normalize();      

靜态成員函數的特點

  • 沒有this指針
  • 不能夠直接存取其雷鐘德非靜态成員
  • 不能被聲明為const、volatile或virtual
  • 不需要經由類對象被調用(但可以通過類對象調用)

靜态成員函數的位址

  • 如果取一個靜态成員函數的位址,獲得的将是其在記憶體中的位置,也就是其位址
  • 由于靜态成員函數沒有this指針,是以其位址的類型并不是一個“指向類成員函數的指針”,而是一個“非成員函數指針”
  • 也就是說:
&Point3d::object_count();      
  • 會得到一個數值,類型是:
unsigned int (*)();      
  • 而不是:
unsigned int (Point3d::*)();      
  • 靜态成員函數由于缺乏this指針,是以差不多等同于非成員函數。它提供了一個好處:成為一個回調(callback)函數