每當建立一個交易對象,在審計日誌中也要建立一筆適當記錄。下面是一個看起來比較合理的做法:
class Transaction
{
public:
Transaction()
{
init();
}
virtual void logTransaction() const { std::cout << "transaction" ;};
protected:
private:
void init()
{
logTransaction();
}
};
class BuyTransaction : public Transaction
{
public:
BuyTransaction():a(5){}
virtual void logTransaction() const { std::cout << "BUytransaction"<< a;};
protected:
private:
int a;
};
class SellTransaction : public Transaction
{
public:
virtual void logTransaction() const;
protected:
private:
};
inline void testItem9()
{
BuyTransaction b;
}
base class構造期間virtual函數絕不會下降到derived classes階層。取而代之的是,對象的作為就像隸屬base類型一樣。
非正式的說法:在base class構造期間,virtual函數不是virtual函數。
由於base class 的建構函式的執行更早於derived class建構函式,當base class的建構函式執行時,derived class的成員變數尚未初始化。如果期間調用的virtual函數下降至derived class階層, derived class的函數幾乎必然取用local成員變數,而那些成員變數尚沒有初始化。這會導致不明確行為和徹夜調試。
其實還有更根本的原因,在derived對象的base class 構造期間,對象的類型是base class而不是derived class。不只virtual函數會被編譯器解析至base class,若使用運行期類型資訊(runtime type information,例如dynamic_cast和typeid),也會把對象視為base class類型。本例中當Transaction建構函式正執行起來打算初始化“BuyTransaction對象內的base class成分”時,該對象的類型是Transaction。這個對象內的“BuyTransaction專屬成分”尚未被初始化,所以面對這些,最安全的做法是視他們不存在。對象在derived class建構函式開始執行之前不會成為一個derived class對象。
相同道理也適用於解構函式。一旦derived class 解構函式開始執行, 對象內的derived class成員變數便呈現未定義值,
所以c++視他們彷彿不存在,進入base class解構函式後,對象就變成一個base class對象。
如何確保每次一有Transaction繼承體繫上的對象被建立,就會有適當版本的logTransaction被調用呢?在建構函式中調用virtual函數是一種錯誤的做法。
其他方案可以解決這個問題。一種做法是在Transaction內將logTransaction修改為non-virtual,然後要求derived class 構造
函數傳遞必要的資訊給Transaction建構函式, 然後那個建構函式便可安全的調用non-virtual的logTransaction。像這樣:
class Transaction
{
public:
explicit Transaction(const std::string &logInfo);
void logTransaction(const std::string &logInfo) const;
protected:
private:
};
Transaction::Transaction(const std::string &logInfo)
{
logTransaction(logInfo)
}
class BuyTransaction : public Transaction
{
public:
BuyTransaction(parameters)
: Transaction(createLogString(parameters))
{
...;
}
protected:
private:
static std::string createLogString(parameters);
int a;
};
換句話說由於你無法使用virtual函數從base class向下調用,在構造期間,你可以藉由“令derived class將必要的構造資訊向上傳遞至base class建構函式”替換之而加以彌補。
注意本例之BuyTransaction內的private static函數createLogString的運用。是的,比起在成員初始化列表裡給予base class
所需資料。利用輔助函數建立一個值傳給base class建構函式往往比較方便。令此函數為static,也就不可能意外指向“初值未成熟的BuyTransaction對象內尚未初始化的成員變數”。
在建構函式和解構函式期間不要調用virtual函數,因為這類調用從不下降至derived class(比起當前執行構造和析構的那層)。