轉眼間第二章的守則也介紹了一半,今天進入的則是第9條守則。也可以不時去回顧一下前面的守則:[Day 1] 前言。
這個守則是:
Never call virtual functions during construction and destruction
字面好理解,廢話不多說,直接來看為什麼?
情境題:如果在base class constructor中call了一個virtual function,那當derived class被construct的時候,那個virtual function會執行的究竟是base class還是derived class的行為呢?
例如以下例子:
clas Transaction
{
public:
Transaction();
virtual void logTransaction () const = 0;
...
}
Transaction::Transaction()
{
...
logTransaction(); // call the virtual function!!
}
class BuyTransaction: public Transaction
{
public:
virtual void logTransaction () const override; // override the virtual function
}
class SellTransaction: public Transaction
{
public:
virtual void logTransaction () const override; // override the virtual function
}
此時我們創造一個BuyTransaction
:
BuyTransaction b;
那它執行到logTransaction
時,執行的會是Transaction::logTransaction
還是BuyTransaction::logTransaction
(總不會是SellTransaction::logTransaction
)呢?
選項A. BuyTransaction::logTransaction
: 這不就是virtual function的意義嗎?
選項B. Transaction::logTransaction
: 然後就會發生link error,因為這個pure virtual function沒有被定義。
答案:這樣排序答案就一定是BXD
如同在有提到過,我們知道derived class在constructor中建構的順序是base class constructor> derived class constructor,所以derived class在一開始建構base class部分的時候,它其實等同於在建構一個base class,所以call的也會是base class的function;畢竟因為還沒輪到derived class部分的建構,那virtual function自然也還沒被建構好。不只是virtual function,只要在base class construct的過程,都視同是在base class做處理(例如dynamic_cast
)。可以這麼說,derived class在它的derived class constructor還沒開始前都還不是derived class。
而為什麼不要在destructor call virtual function,想必也可以照上面的脈絡推出來了吧!destructor運行的順序跟constructor相反,會先destruct derived class的部分,然後才是base class;因此,當運行到base class的destructor時,dervied的部分,包含那些virtual function,也已經沒了,在運行到base class的destructor時,它就已經是base class的形狀跟性質惹。
而上面那則範例很明顯的違反了這條守則,有些compilers會發出warning,也有些不會。有些時候這條守則會違反的沒那麼隱晦,例如它在constructor call的一系列function最終會引向一個virtual function。例如這樣:
class Transaction
{
public:
Transaction(){init();}
private:
void init()
{
...
logTransaction();
}
}
它其實跟前面的結果會一樣,但更難察覺。它可能link的時候也不會有error,一直到runtime才中斷程式。而如果logTransaction
不是pure virtual function,它更不會報錯,只是每次都執行到base class的版本,讓人很難去察覺為什麼結果跟預想不同。那要如何解決呢?
最簡單就是遵守我們的守則─ 不要在constructor跟destructor所有call,包含他們過程中會call到的所有過程去用到virtual function。
那像上面的範例,為了不要讓constructor去call到virtual function,然後又要讓不同的version call到不同的事,一種做法就是把變動的部分放入construct需要的參數放入,例如這樣:
class Transaction
{
publicL
explicit Transaction(const std::string& logInfo);
void logTransaction(const std::string& logInfo);
}
不用再用derived class去寫不同的logTransaction
版本。
另外,也可以把function變成static的,就能避免去用到還沒被建構出來的部分。
貼心重點提醒:
- Don't call virtual functions during construction or destruction, because such calls will never go to a more derived class than that of the currently executing constructor or destructor.
另外也想分享一下如果確保在constructor內要call的就是base class版本的該virtual function無誤,也可以考慮直接寫明call Transaction::logTransaction
,這樣大家也都不會有疑慮。