絕對不要在 構造函數/析構函數 中調用虛函數


今天為了查一個重復delete的bug,在析構函數中調用了一個虛函數 toString,想在對象析夠前打印對象信息,結果發現打印出來全都是基類的,后來仔細研究了這個問題,先說結論:

 

1,絕對不要在構造函數和析構函數中調用虛函數,他們都不是動態綁定的。

2,如果析構函數是虛函數,那么可以看到類似動態綁定的效果,但這並不是動態綁定,也並不意味着我們可以隨意在析構函數中調用、間接調用虛函數

3,如果基類存在虛函數,析構函數必須為虛函數,更進一步的講,如果程序可能會存在 base *p = new drived() 這樣的語句,即使沒有虛函數也要有虛析構函數

 

4, 為什么會產生這樣的效果??

      因為vptr的綁定是發生在對象構造期間的,所以構造函數會有這樣一個假設: 我正在構造的對象和我的類型是完全一致的, 同理虛析構函數也會這么假設。所以在構造/析構函數中調用virtual 函數時不會有任何多態效果的。

 

5, 那為什么虛析構函數中調用虛函數會有多態??

      因為析構函數本身如果是虛函數的,在執行虛函數之前會通過vptr找到正確的析構函數,然而在那個析構函數調用的虛函數依舊是沒有動態綁定。我們看到的"動態綁定"是虛析構函數的動態綁定,而不是析構函數所調用的虛函數的多態

 

6,考慮下面的代碼:

class Base {
public:
    virtual void wtf() { cout << "base" << endl; }
    virtual ~Base() { wtf(); }
};

class Drived : public Base {
public:
    void wtf() { cout << "drived" << endl; }
    ~Drived () { wtf(); }
};


int main() {
    Base *p = new Drived();
    delete p;
    return 0;
}

         輸出為 drived  base, 看起來似乎一切正常,但是實際上的運行過程是:由於析構函數動態綁定,所以在delete p時,發現對象實際類型為drived,所以先析構派生類,再析構基類, 所以執行drived::~drived(), 在drived::~drived() 中看見了一個叫wtf()的函數,這個時候不會去查vtab,直接認為它是drived::wtf(),之后執行base::~base()同理。

        但是如果析構函數不是虛函數,那么就只會執行base::~base() 和 base::wtf(),哪怕 wtf是虛函數。更進一步的,凡是代碼中發生類似base *p  = new dirved()情況的,幾乎都必須要有一個虛析構函數。否則就會出現delete p時只會delete基類部分。。。

        更更更擴展的是,使用 delete [] p 時,為了判斷delete的次數和位置, 即使有虛析構函數還是會出問題,因為具體在哪些位置delete 是未定義的。。

        同樣的,在函數構造是,會先執行基類構造函數,和基類對應版本的函數,然后是子類的。

 

 

所以不要嘗試在  構造/析構 函數中調用虛函數, 這會讓程序的意義非常不明確。如果非得調用,顯式說明我要調用那個版本的,如base::xx()


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM