《深度探索C++對象模型》:member functions

        在第一篇文章中我們談到member function有三種:nonstaticstaticvirtual。前面兩種並不是我們今天的主角,我們要學習的是virtual member functions。

nonstatic member functions

        C++的設計準則之一就是:nonstatic member function至少必須和一般的nonmember function有相同的效率,實際上member function被編譯器內化爲nonmember的形式。member function的調用必須經由一個class object才能操作,這點與static mmber function不同。

static member functions

一個static member function的主要特性是它沒有this指針,並且由此衍生出來的其他特性有:

        1、它不能夠直接存取class中的nonstatic member;

        2、它不能夠被聲明爲const、volatile或virtual;

        3、它的調用不需要經由class object(雖然大部分時候它是這麼被調用的)。

// 如果Point3d::normalize()是一個static member function
obj.normalize();
ptr->normalize();
// 那麼以上兩個調用操作,將會被轉換爲一般的nonmember函數的調用。

單一繼承下的virtual functions

        在之前的文章中,我們或多或少的瞭解了virtual的一般實現模:每一個class有一個vtbl,內含class的virtual functions的地址,每個class object有一個vptr,指向vtbl
我們來看一個例子:

class Point {
public:
    virtual ~Point();
    virtual Point& mult(float) = 0;
    // ...
    float x() { return _x; }
    virtual float y() const { return 0.0F; }
    virtual float z() const { return 0.0F; }
    // ...
protected:
    Point(float x = 0.0F);
    float _x;
};

class Point2d : public Point {
public:
    Point2d(float x = 0.0F, float y = 0.0F) : Point(x), _y(y) {}
    ~Point2d();

    Point2d& mult(float);
    float y() const { return _y; }
protected:
    float _y;
};

        顯然這是一個單一繼承的例子,很簡單。那麼就有一個問題來了,當我們調用某個virtual member function譬如說ptr->y()的時候,那我們調用的是classPointobjecty()還是classPoint2dobjecty()呢?也就是說,y()是一個virtual member function,我們知道RTTI的特性就是某些信息只有在執行期才能確定,而在編譯期是不能確定的。

        顯然,不管編譯期還是執行期,若我們要正確的執行y()的實例,那麼整個過程中需要知道:

                1、ptr所指對象的真實類型;

                2、y()實例的位置。

        根據前面幾節對象模型學習,我們知道在C++對象模型中,在編譯期編譯期會爲我們構建兩個東西:vptrvtbl,在程序執行時表格的大小和內容是不會發生改變的。由於要執行y()函數,可以由兩個步驟來完成這項任務(即vptrvtbl的構建):

               1、爲了找到表格,每一個class object被安插了一個由編譯器內部產生的指針,指向該表格;

                2、爲了找到該函數地址,每一個virtual function被指派一個表格索引值。

        當然,以上的工作都是有編譯器在編譯期完成的,那麼在執行期所要做的就是,只在特性的virtual table slot中激活virtual function。

        根據上面的對象模型,我們在編譯期就構建了vptr和vtbl,而且已經知道了某個函數的索引值,因此唯一一個在執行期才能知道的東西就是:slot所指的到底是哪一個函數實例,當然執行期ptr的具體指向會爲我們解決這個問題。

class Point3d : public Point2d {
public:
    Point3d(float x = 0.0F, float y = 0.0F, float z = 0.0F) : Point2d(x, y), _z(z) {}
    ~Point3d();

    Point3d& mult(float);
    float z() const { return _z; }
    // ...
protected:
    float _z;
};


在單一繼承中,virtual function機制的行爲非常良好的,不但有效率而且很容易被塑模出來。

多重繼承下的virtual functions

對於多重繼承,我們還是直接來看一個例子。

class Base1 {
public:
    Base1();
    virtual ~Base1();
    virtual void speakClearly();
    virtual Base1* clone() const;
protected:
    float data_Base1;
};

class Base2 {
public:
    Base2();
    virtual ~Base2();
    virtual void mumble();
    virtual Base1* clone() const;
protected:
    float data_Base2;
};

class Derived : public Base1, public Base2 {
public:
    Derived();
    virtual ~Derived();
    virtual Derived* clone() const;
protected:
    float data_Derived;
};


在多重繼承之下,對象模型的機制是:一個derived table slot內含n-1個額外的virtual tables,n表示其上一層base classes的個數(因此,單一繼承將不會有額外的virtual tables)。對於本例的Derived而言,會有兩個virtual tables被編譯器產生出來:

        1、一個主要實例,與Base1(最左端base class)共享;

        2、一個次要實例,與Base2(第二個base class)有關。

針對每一個virtual tables,Derived對象中有對應的vptr,具體對象模型如下:


虛擬繼承下的virtual functions

在之前的文章中,我們大概也見識到了虛擬繼承的特殊性,class subobject作爲共享部分放在最後面,而在vtpr所指向的vtbl中的開頭添加了表示offset的項。下面還是來看一個現實例子。

class Point2d {
public:
    Point2d(float = 0.0F, float = 0.0F);
    virtual ~Point2d();

    virtual void mumble();
    virtual float z();
    // ...
protected:
    float _x;
    float _y;
};

class Point3d : public virtual Point2d {
public:
    Point3d(float = 0.0F, float = 0.0F, float = 0.0F);
    virtual ~Point3d();

    virtual float z();
    // ...
protected:
    float _z;
};
其對象模型如下:


這樣一來我們就清楚了虛擬繼承下的對象模型。

有一點建議就是:不要再一個virtual base class中聲明nonstatic data members


最後,傳一個在學習《深度探索C++對象模型》過程中記錄的一些模型圖,在這裏:http://download.csdn.net/detail/hujingshuang/9663843


參考資料:

[1] 深度探索C++對象模型,[美]Stanley B. Lippman著,侯捷譯;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章