深度探索c++對象模型之vptr初始化語意學

      接上文,還是這個圖,還是這個繼承關係:

   

其中它們的構造順序是,是從內到外、從根源到末端。所以對一個PVertex對象來說,它的構造順序是:1、Point   2、Point3d  3、Vertex  4、Vertex3d  5、PVertex,然後書中說到【大意】:在以上的每一個類中,都定義兩個虛函數,一個是fun【我自己取的名】,一個是size,其中它倆的關係是,在size中調用fun;然後再在每一個類的構造器中,調用一下size。問:這樣的話,在以上每一個類的構造器中調用的size裏面,會調用PVertex中的fun虛函數【因爲完整類對象就是PVertex,既語句“PVertex pv;”】,還是調用當前構造類中的fun虛函數?如果讀者朋友看到這裏不明白我問的是什麼,那麼我換一種方式提問:比如一句【PVertex pv;】語句,最先執行的是Point類的構造器,那麼在執行Point構造器時,會調用size函數,在這個size函數裏面,究竟是調用的是PVertex::fun虛函數,還是Point::fun虛函數?

      書中首先給出了答案:在執行Point構造器時,裏面調用的fun函數應該是Point中的fun函數,而不應該是PVertex::fun。因爲“經由構造中的對象來調用一個virtual base function,其函數實體應該是在此class中有作用的那個”,“當base class constructor執行時,derived實體還沒有被構造出來。”在PVertex constructor執行完畢之前,pv並不是一個完整的對象,在執行Point constructor完之後,只有Point類對象被構造出來。

      然後,作者提供的解決之道是什麼呢,就是在執行類的constructor時,限制一組virtual function候選名單。文中這樣說道:“想一想,什麼是決定一個class的virtual function名單的關鍵?答案是virtual table。virtual table如何被處理?答案是通過vptr。所以,爲了控制一個class中有所作用的函數,編譯系統只要簡單的控制住vptr的初始化和設定操作即可。當然,設定vptr是編譯器的責任,任何程序員都不必操心此事。”。

      但vptr在什麼時候被設定呢,答案是它當前隸屬的class的constructor調用完成之後,但是要在任何用戶提供的代碼和“member initialization list中所列的members初始化操作”之前。這樣每一個類的構造器都一直等到它的基類構造器執行完畢之後,再去設定自己的vptr,那麼每次它都能夠調用正確的virtual function實體。

      讓每一個base class constructor設定好對象的vptr,使它指向相關聯的virtual table地址,構造中的對象就可以正確的變成“構造過程中所幻化出來的每一個class”對象。比如,一個PVertex會先形成一個Point對象,接着是Point3d、Vertex、Vertex3d,最後才變成一個PVertex完整對象,在此過程中它會調用每一個base class constructor。“個體”的連續概括了“系統”,僅此而已。

     在此過程中,關於vptr的設定,總結爲3點:

1):在派生類對象構造器中,所有的虛基類及上一層基類的構造器都會被調用;

2):在上述工作完成之後,該類的vptr纔會被初始化,指向相關的virtual table;

3):如果有“members initialization list”或者用戶自己定義的代碼,那麼它們都將放置在vptr設定工作完成以後【如果兩者都存在,那麼“members initialization list”先於用戶自定義代碼】,以免發生錯誤的 virtual function調用。

      比如,如果我們定義的PVertex constructor如下:

PVertex::Pvertex(int x, int y, int z)
{
	cout << "PVertex class size is" << size() << endl;
}

那麼它會在編譯器內部被擴展成如下形式:

//注意,以下是C++僞碼
PVertex::Pvertex(PVertex *this,bool _most_deriver,int x, int y, int z)
{
	//條件選擇式決定要不要調用base class constructor
	if (_most_derived != false) this->Point::Point(x, y);

	//調用上一層的基類
	this->Vertex3d::Vertex3d(x, y, z);

	//就在這裏!設定vptr!!
	this->_vptr_PVertex = _vtbl_PVertex;
	this->_vptr_Point_PVertex = _vtbl_Point_PVertex;

	//下面纔是“members initialization list”和用戶自定義代碼
	cout << "PVertex class size is" 
		//以下是經由虛擬機制調用size虛函數
		<< (*this->_vptr_PVertex[3].faddr)(this)
		<< endl;
}

      在兩種情況下,vptr必須被設定好。首先是一個完整類的對象構造完成之前,其次是一個subobject class constructor中要調用一個虛函數。現在的編譯器,有的將constructor分裂爲兩個版本,一個是類完整版,一個是subobject版本,在subobject版本中,vptr的設定可以省略。

      但文中最後也說道:儘管vptr的設定是在用戶代碼或members initialization list之前,但在語意上來說,在constructor中使用虛函數依然有不安全的可能性,因爲也許函數還依賴未被正確初始化的members,所以作者並不推薦文中做法。


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