天天看點

《深度探索C++對象模型》讀書筆記(5)

 ***純虛拟函數***

在設計抽象基類時,需要注意以下幾點:

(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:

《深度探索C++對象模型》讀書筆記(5)
《深度探索C++對象模型》讀書筆記(5)

class  Point  ... {

《深度探索C++對象模型》讀書筆記(5)

public:

《深度探索C++對象模型》讀書筆記(5)
《深度探索C++對象模型》讀書筆記(5)

Point(float x = 0.0, float y = 0.0) : _x(x),_y(y) ...{}

《深度探索C++對象模型》讀書筆記(5)

virtual float z();

《深度探索C++對象模型》讀書筆記(5)

protected:

《深度探索C++對象模型》讀書筆記(5)

float _x,_y;

《深度探索C++對象模型》讀書筆記(5)

} ;

你可不能小看z()這個virtual function給class Point帶來的巨大變化。virtual function的引入促使每一個class Point擁有一個vtpr,這樣一來,編譯器在constructor中添加了對vptr進行初始化的代碼,而copy constructor和copy assignment operator也會對vptr進行設定,而不再是原先簡單的bitwise操作了。

請看以下的代碼:

《深度探索C++對象模型》讀書筆記(5)

Point foobar()

《深度探索C++對象模型》讀書筆記(5)
《深度探索C++對象模型》讀書筆記(5)

... {

《深度探索C++對象模型》讀書筆記(5)

Point local;

《深度探索C++對象模型》讀書筆記(5)

Point *heap = new Point;

《深度探索C++對象模型》讀書筆記(5)

*heap = local;

《深度探索C++對象模型》讀書筆記(5)

delete heap;

《深度探索C++對象模型》讀書筆記(5)

return local;

《深度探索C++對象模型》讀書筆記(5)

}

将被内部轉化為:

《深度探索C++對象模型》讀書筆記(5)

Point foobar(Point  & _result)

《深度探索C++對象模型》讀書筆記(5)
《深度探索C++對象模型》讀書筆記(5)

... {

《深度探索C++對象模型》讀書筆記(5)

Point local;

《深度探索C++對象模型》讀書筆記(5)

local.Point::Point();

《深度探索C++對象模型》讀書筆記(5)

Point *heap = _new(sizeof(Point));

《深度探索C++對象模型》讀書筆記(5)

if(heap != 0)

《深度探索C++對象模型》讀書筆記(5)

heap->Point::Point();

《深度探索C++對象模型》讀書筆記(5)

*heap = local;

《深度探索C++對象模型》讀書筆記(5)

_result.Point::Point(local);  // copy constructor的應用

《深度探索C++對象模型》讀書筆記(5)

local.Point::~Point();

《深度探索C++對象模型》讀書筆記(5)

return;

《深度探索C++對象模型》讀書筆記(5)

}

從以上代碼的轉化可以看出:一般而言,如果你的設計之中有很多函數都需要以傳值方式(by value)傳回一個local class object,那麼提供一個copy constructor就比較合理。

***繼承體系下的對象構造***

假設class Point3d虛拟繼承于class Point,但由于class Point僅存在一份實體,因而class Point3d的constructor需要注意一個問題。

請看下面的繼承關系圖:

《深度探索C++對象模型》讀書筆記(5)
《深度探索C++對象模型》讀書筆記(5)

class  Point3d :  virtual   public  Point  ... { ... } ;

《深度探索C++對象模型》讀書筆記(5)
《深度探索C++對象模型》讀書筆記(5)

class  Vertex :  virtual   public  Point  ... { ... } ;

《深度探索C++對象模型》讀書筆記(5)
《深度探索C++對象模型》讀書筆記(5)

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的調用操作。

《深度探索C++對象模型》讀書筆記(5)

//  在virtual base class情況下的constructor擴充内容

《深度探索C++對象模型》讀書筆記(5)

Vertex3d *  Vertex3d::Vertex3d(Vertex3d  * this ,  bool  _most_derived,  float  x,  float  y,  float  z)

《深度探索C++對象模型》讀書筆記(5)
《深度探索C++對象模型》讀書筆記(5)

... {

《深度探索C++對象模型》讀書筆記(5)

if(_most_derived != false)

《深度探索C++對象模型》讀書筆記(5)

this->Point::Point(x,y);

《深度探索C++對象模型》讀書筆記(5)
《深度探索C++對象模型》讀書筆記(5)

// 在調用上一層base classes的constructor之前設定_most_derived為false

《深度探索C++對象模型》讀書筆記(5)

this->Point3d::Point3d(false,x,y,z);

《深度探索C++對象模型》讀書筆記(5)

this->Vertex::Vertex(false,x,y,z);

《深度探索C++對象模型》讀書筆記(5)
《深度探索C++對象模型》讀書筆記(5)

// 設定vptr

《深度探索C++對象模型》讀書筆記(5)

// 安插user code

《深度探索C++對象模型》讀書筆記(5)
《深度探索C++對象模型》讀書筆記(5)

return this;

《深度探索C++對象模型》讀書筆記(5)

}

為了控制一個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的情況下,編譯器才會自動合成出一個來。

其解構順序與建構順序正好相反。