Effective C++讀書筆記之九:絕不在構造和析構過程中調用virtual

Item 09:Never call virtual functions during construction or destruction

假設你有個class繼承體系,用來塑模股市交易如買進、賣出的訂單等等。這樣的交易一定要經過審計,所以每當創建一個交易對象,在審計日誌中也需要創建一筆適當記錄。下面是一個看起來頗爲合理的做法:

class Transaction
{
public:
	Transaction();
	virtual void logTransaction() const = 0;//做出一份因類型不同而不同的交易記錄 
	
	...
};
Transaction::Transaction()
{
	...
	logTransaction();    //最後動作是志記這筆交易 
}

class BuyTransaction:public Transaction
{
public:
	virtual void logTransaction() const;
	...
};

現在,當一下這行被執行,會發生什麼事:

BuyTransaction b;

無疑會有一個BuyTransaction構造函數被調用,但首先Transaction構造函數一定會更早地被調用。此時Transaction構造函數的最後一行調用的logTransaction是Transaction內的版本,不是BuyTransaction的版本。

同樣的情況也使用與析構函數。

但是如果不這麼做的話,你如何確保每次一有Transaction繼承體系上的對象被創建,就會有適當版本的logTransaction被調用呢?

一種做法是在class 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;
	
	...
};

Transaction::Transaction(std::string& logInfo)
{
	...
	logTransaction(logInfo);
}

class BuyTransaction:public Transaction
{
public:
	BuyTransaction(parameters):Transaction(creatrLogString(parameters))
	{...}
	...
private:
	static std::string createLogString(parameters);
}

請注意本例之BuyTransaction內的private static函數的運用。比起在成員初值列內給予base class所需數據,利用輔助函數創建一個值傳給base class構造函數往往比較方便(也比較可讀)。令此函數爲static,也就不可能意外指向:初期爲成熟之BuyTransaction對象內尚未初始化的成員變量“。這很重要,正是因爲:那些成員變量處於未定義狀態”,所以“在base class構造和析構期間調用的virtual函數不可下降至derived classes。”


//雲風評註:不小心在構造函數或析構函數中間接調用虛函數是C++中常見的錯誤,所以最好在編碼規範中嚴格限制構造函數和析構函數可以做的事情。把複雜的對象初始化過程搬到一個獨立的初始化函數當中,並有使用者顯示調用,是個不錯的建議。在構造函數和析構函數中只做簡單而意義明確的對資源引用和解引用的工作即可。
請記住:

在構造和析構期間不要調用virtual函數,因爲這類調用從不下降至derived class。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章