在第一篇文章中我們談到member function有三種:nonstatic、static和virtual。前面兩種並不是我們今天的主角,我們要學習的是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()的時候,那我們調用的是classPointobject的y()還是classPoint2dobject的y()呢?也就是說,y()是一個virtual member function,我們知道RTTI的特性就是某些信息只有在執行期才能確定,而在編譯期是不能確定的。
顯然,不管編譯期還是執行期,若我們要正確的執行y()的實例,那麼整個過程中需要知道:
1、ptr所指對象的真實類型;
2、y()實例的位置。
根據前面幾節對象模型學習,我們知道在C++對象模型中,在編譯期編譯期會爲我們構建兩個東西:vptr和vtbl,在程序執行時表格的大小和內容是不會發生改變的。由於要執行y()函數,可以由兩個步驟來完成這項任務(即vptr和vtbl的構建):
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著,侯捷譯;