你不應(yīng)該在構(gòu)造或析構(gòu)期間調(diào)用虛函數(shù),因?yàn)檫@樣的調(diào)用不會(huì)如你想象那樣工作,而且它們做的事情保證會(huì)讓你很郁悶。 假設(shè)你有一套模擬股票處理的類層次結(jié)構(gòu),例如,購(gòu)入流程,出售流程等。對(duì)這樣的處理來說可以核查是非常重要的,所以隨時(shí)會(huì)創(chuàng)建一個(gè) Transaction 對(duì)象,將這個(gè)創(chuàng)建記錄在核查日志中是一個(gè)適當(dāng)?shù)囊蟆O旅媸且粋€(gè)看起來似乎合理的解決問題的方法:
class Transaction { // base class for all public: // transactions Transaction();
virtual void logTransaction() const = 0; // make type-dependent // log entry ... };
Transaction::Transaction() // implementation of { // base class ctor ... logTransaction(); // as final action, log this } // transaction
class BuyTransaction: public Transaction { // derived class public: virtual void logTransaction() const; // how to log trans- // actions of this type ... };
class SellTransaction: public Transaction { // derived class public: virtual void logTransaction() const; // how to log trans- // actions of this type ... }; |
考慮執(zhí)行這行代碼時(shí)會(huì)發(fā)生什么:
很明顯 BuyTransaction 的構(gòu)造函數(shù)會(huì)被調(diào)用,但是首先,Transaction 的構(gòu)造函數(shù)必須先被調(diào)用,派生類對(duì)象中的基類部分先于派生類部分被構(gòu)造。Transaction 的構(gòu)造函數(shù)的最后一行調(diào)用虛函數(shù) logTransaction,但是結(jié)果會(huì)讓你大吃一驚,被調(diào)用的 logTransaction 版本是在 Transaction 中的那個(gè),而不是 BuyTransaction 中的——即使被創(chuàng)建的對(duì)象類型是 BuyTransaction。基類構(gòu)造期間,虛函數(shù)從來不會(huì)向下匹配(go down)到派生類。取而代之的是,那個(gè)對(duì)象的行為就好像它的類型是基類。非正式地講,
基類構(gòu)造期間,虛函數(shù)禁止。 這個(gè)表面上看起來匪夷所思的行為存在一個(gè)很好的理由。因?yàn)榛惖臉?gòu)造函數(shù)在派生類構(gòu)造函數(shù)之前執(zhí)行,當(dāng)基類構(gòu)造函數(shù)運(yùn)行時(shí),派生類數(shù)據(jù)成員還沒有被初始化。如果基類構(gòu)造期間調(diào)用的虛函數(shù)向下匹配(go down)到派生類,派生類的函數(shù)理所當(dāng)然會(huì)涉及到本地?cái)?shù)據(jù)成員,但是那些數(shù)據(jù)成員還沒有被初始化。這就會(huì)
為未定義行為和悔之晚矣的調(diào)試噩夢(mèng)開了一張通行證。調(diào)用涉及到一個(gè)對(duì)象還沒有被初始化的部分自然是危險(xiǎn)的,所以 C++ 告訴你此路不通。
在實(shí)際上還有比這更多的更深層次的原理。在派生類對(duì)象的基類構(gòu)造期間,對(duì)象的類型是那個(gè)基類的。
不僅虛函數(shù)會(huì)解析到基類,而且語(yǔ)言中用到運(yùn)行時(shí)類型信息(runtime type information)的配件(例如,dynamic_cast和 typeid),也會(huì)將對(duì)象視為基類類型。在我們的例子中,當(dāng) Transaction 構(gòu)造函數(shù)運(yùn)行初始化 BuyTransaction 對(duì)象的基類部分時(shí),對(duì)象的類型是 Transaction。C++ 的每一個(gè)配件將以如下眼光來看待它,并對(duì)它產(chǎn)生這樣的感覺:對(duì)象的 BuyTransaction 特有的部分還沒有被初始化,所以安全的對(duì)待它們的方法就是視若無睹。在派生類構(gòu)造函數(shù)運(yùn)行之前,一個(gè)對(duì)象不會(huì)成為一個(gè)派生類對(duì)象。
同樣的原因也適用于析構(gòu)過程。一旦派生類析構(gòu)函數(shù)運(yùn)行,這個(gè)對(duì)象的派生類數(shù)據(jù)成員就被視為未定義的值,所以 C++ 就將它們視為不再存在。在進(jìn)入基類析構(gòu)函數(shù)時(shí),對(duì)象就成為一個(gè)基類對(duì)象,C++ 的所有配件——虛函數(shù),dynamic_casts 等——都如此看待它。