iT邦幫忙

2023 iThome 鐵人賽

DAY 13
0
自我挑戰組

Effective C++ 讀書筆記系列 第 13

[Day 13] Never call virtual functions during construction and destruction

  • 分享至 

  • xImage
  •  

前言

轉眼間第二章的守則也介紹了一半,今天進入的則是第9條守則。也可以不時去回顧一下前面的守則:[Day 1] 前言

不要在constructor, destructor call virtual funcion的理由

這個守則是:

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,這樣大家也都不會有疑慮。


上一篇
[Day 12] Prevent exceptions from leaving destructors
下一篇
[Day 14] Have assignment operators return a reference to *this
系列文
Effective C++ 讀書筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言