***純虛擬函數***
在設計抽象基類時,需要注意以下幾點:
(1)不要將destructor 聲明為pure virtual function;
如果將destructor聲明為pure virtual function,則設計者一 定得定義它。因為每一個derived class destructor會被編譯器加以擴展,以靜態調用得方式調用其 “每一個virtual base class”以及“上一層base class”的 destructor.
(2)不要將那些函數定義內容並不與類型有關的函數設計為virtual function,因 為其幾乎不會被後繼的derived class改寫。
(3)對於其derived class可能修改某一個data member的函數,不應被聲明為const.
***“無繼承”情況下的對象構造***
先 定義class Point:
class Point {
public:
Point(float x = 0.0, float y = 0.0) : _x(x),_y(y) {}
virtual float z();
protected:
float _x,_y;
};
你可不能小看z()這個virtual function給class Point帶來的巨大變化。virtual function的引入促使每一個class Point擁有一個vtpr,這樣一來,編譯器在constructor中添加了對 vptr進行初始化的代碼,而copy constructor和copy assignment operator也會對vptr進行設定,而不 再是原先簡單的bitwise操作了。
請看以下的代碼:
Point foobar()
{
Point local;
Point *heap = new Point;
*heap = local;
delete heap;
return local;
}
將被內部轉化為:
Point foobar(Point &_result)
{
Point local;
local.Point::Point();
Point *heap = _new (sizeof(Point));
if(heap != 0)
heap->Point::Point();
*heap = local;
_result.Point::Point(local); // copy constructor的應用
local.Point::~Point();
return;
}
從以上代碼的轉化可以看出:一般而言,如果你的設計之中有很多函 數都需要以傳值方式(by value)傳回一個local class object,那麼提供一個copy constructor就比 較合理。
***繼承體系下的對象構造***
假設class Point3d虛擬繼承於class Point,但 由於class Point僅存在一份實體,因而class Point3d的constructor需要注意一個問題。
請看下面的繼承關系圖:
class Point3d : virtual public Point { ... };
class Vertex : virtual public Point { ... };
class Vertex3d : public Point3d, public Vertex { ... };
通常來說,class Point3d和class Vertex的constructor均需調用Point的 constructor,然而,當Point3d和Vertex同為Vertex3d的subobject時,它們對Point constructor的調 用操作一定不可以發生,而是交由Vertex3d來完成。
那麼如何做到這一點呢?其實只需在 constructor中添加一個參數就可以了。例如,class Vertex3d在調用Point3d和Vertex的constructor之 前,總是會把參數_most_derived設為false,於是就壓制了兩個constructors中對Point constructor的 調用操作。
// 在virtual base class情況下的constructor擴充內容
Vertex3d* Vertex3d::Vertex3d(Vertex3d *this, bool _most_derived, float x, float y, float z)
{
if(_most_derived != false)
this->Point::Point(x,y);
// 在調用上一層 base classes的constructor之前設定_most_derived為false
this->Point3d::Point3d (false,x,y,z);
this->Vertex::Vertex(false,x,y,z);
// 設定vptr
// 安 插user code
return this;
}
為了控制一個class中有所作用的函數, 編譯器只要簡單地控制住vptr的初始化和設定操作即可。
vptr初始化操作應該如何處理?實際情 況是:應該在base class constructors(具體來說,是所有的virtual base classes和上一層的base classes)調用操作之後,但是在程序員供應的碼或是“member initialization list中所列的 members初始化操作”之前。為什麼是這樣呢?
如果每一個constructor都一直等待到其 base class constructors執行完畢之後才設置其對象的vptr,那麼每次它都能夠調用正確的virtual function實體。
constructor的執行算法通常如下:
(1)在derived class constructor 中,所有virtual base classes的constructor會被調用;
(2)在derived class constructor 中,上一層base class的constructor會被調用;
(3)上述完成之後,對象的vptr(s)被初始 化,指向相關的virtual table(s);
(4)如果class有member class object,而後者擁有 constructor,那麼它們會以其聲明順序的相反順序被調用;(5)用戶所定義的代碼。
下面是 vptr必須被設定的兩種情況:
(1)當一個完整的對象被構造起來時,如果我們聲明一個Point對 象,Point constructor必須設定其vptr;
(2)當一個subobject constructor調用了一個 virtual function(無論是直接調用或間接調用)時。
在明確了哪些情況下vptr必須被設定,我 們在聲明一個PVertex時,各個vptr不再需要在每一個base class constructor中被設定。解決之道是把 constructor分裂為一個完整的object實體和一個subobject實體。在subobject實體中,vptr的設定可以 忽略。
如果我們不對Point供應一個copy assignment operator,而光是依賴默認的memberwise copy,編譯器通常不會產生出一個實體,除非class不表現出bitwise語意。關於哪些情況class不表現出 bitwise語意,請參見讀書筆記(2)。
由於在virtual base class的拷貝操作將造成subobject 的多重拷貝,並且該問題至今難以解決。因此筆者的建議是:盡量不要允許一個virtual base class的 拷貝操作,甚至建議:不要在任何virtual base class中聲明數據。
***解構語意學***
如果class沒有定義destructor,那麼只有在class內含的member object(或是class自己的base class )擁有destructor的情況下,編譯器才會自動合成出一個來。
其解構順序與建構順序正好相反。