這個問題來自於《Effective C++》條款9:永遠不要在構造函數或析構函數中調用虛函數 。
假設有如下代碼:
class Transaction {// 所有交易的基類 public: Transaction(); virtual void logTransaction() const = 0;//建立依賴於具體交易類型的登錄項 ... }; Transaction::Transaction() //實現基類的構造函數 { ... logTransaction(); //最后,登錄該交易 } class BuyTransaction: public Transaction { // 派生類 public: virtual void logTransaction() const; //怎樣實現這種類型交易的登錄? ... }; class SellTransaction: public Transaction { //派生類 public: virtual void logTransaction() const; //怎樣實現這種類型交易的登錄? ... };
現在,請分析執行下列代碼調用時所發生的事情:
BuyTransaction b;
很明顯,一個 BuyTransaction類構造器被調用。但是,首先調用的是Transaction類的構造器-派生類對象的基類部分是在派生類部分之前被構造的。 Transaction構造器的最后一行調用了虛函數logTransaction,但是奇怪的事情正是在此發生的。被調用函數 logTransaction的版本是Transaction中的那個,而不是BuyTransaction中的那個-即使現在產生的對象的類型是 BuyTransaction,情況也是如此。在基類的構造過程中,虛函數調用從不會被傳遞到派生類中。代之的是,派生類對象表現出來的行為好象其本身就是基類型。不規范地說,在基類的構造過程中,虛函數並沒有被"構造"。
簡單的說就是,在子類對象的基類子對象構造期間,調用的虛函數的版本是基類的而不是子類的。
對上面這種看上去有點違背直覺的行為可以用一個理由來解釋-因 為基類構造器是在派生類之前執行的,所以在基類構造器運行的時候派生類的數據成員還沒有被初始化。如果在基類的構造過程中對虛函數的調用傳遞到了派生類, 派生類對象當然可以參照引用局部的數據成員,但是這些數據成員其時尚未被初始化。這將會導致無休止的未定義行為和徹夜的代碼調試。沿類層次往下調用尚未初始化的對象的某些部分本來就是危險的,所以C++干脆不讓你這樣做。
在對象的析構期間,存在與上面同樣的邏輯。一旦一個派生類的析構器運行起來,該對象的派生類數據成員就被假設為是未定義的值,這樣以來,C++就把它們當做是不存在一樣。一旦進入到基類的析構器中,該對象即變為一個基類對象,C++中各個部分(虛函數,dynamic_cast運算符等等)都這樣處理。
以上是《Effective C++》的部分。其實當基類自子對象構造完畢之后,進入子類對象的構造期間調用的虛函數就是子類覆蓋的版本了。即有如下代碼所示:
class Base { public: Base() { Fuction(); } virtual void Fuction() { cout << "Base::Fuction" << endl; } }; class A : public Base { public: A() { Fuction(); } virtual void Fuction() { cout << "A::Fuction" << endl; } }; // 這樣定義一個A的對象,會輸出什么? A a;
輸出為:
Base::Fuction
A::Fuction
那么編譯器是如何實現的呢?
在基類子對象構造期間,編譯器直接調用基類版本的虛函數。在子類對象構造期間,編譯器直接調用基類虛函數的版本。並沒有走虛機制。雖然在基類子對象的構造期間,虛表指針指向的是基類的Virtual Table,在進入子類對象構造后,虛表指針指向的是子類對象的Virtual Table。詳見http://blog.csdn.net/magictong/article/details/6734241
參考資料:
1. http://blog.csdn.net/hxz_qlh/article/details/14089895
2. http://blog.csdn.net/magictong/article/details/6734241