程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> Effective C++(7) 為多態基類聲明virtual析構函數 or Not

Effective C++(7) 為多態基類聲明virtual析構函數 or Not

編輯:C++入門知識

問題聚焦:

已經對一個對象執行了delete語句,還會發生內存洩漏嗎?

先來看個demo:
// 計時器類
class TimeKeeper {
public:
    TimeKeeper();
    ~TimeKeeper();
};
class AtomicClock: public TimeKeeper { ...... };    // 原子鐘
class WaterClock: public TimeKeeper { ...... };      // 水表
class WristWatch: public TimeKeeper { ...... };      // 腕表

// 設計工廠函數以供用戶使用
TimeKeeper* ptk = getTimeKeeper();     // Factory函數會“返回一個父類的指針,指向新生成的子類對象”
......
delete ptk;           // Point! 釋放它,避免資源洩漏


上面的這個demo有什麼問題呢? 內存洩漏?後面已經delete掉這個對象了,還會內存洩漏嗎?答案是肯定的。 讓我們分析一下。 問題描述:getTimeKeeper()函數返回的指針指向一個derived class對象,而那個子類對象經由它的父類指針被釋放,而它的父類有個non-virtual析構函數。 導致結果:詭異的“局部銷毀” C++指出,當子類對象經由一個它的父類對象指針被刪除,而該父類對象的析構函數為non-virtual,其結果是:通常情況下,該對象的父類部分被銷毀,而子類部分沒有被銷毀。 解決方案:父類的析構函數聲明為virtual函數。 Demo:
class TimeKeeper {
public:
    TimeKeeper();
    virtual ~TimeKeeper();
    ......
};

// 使用
TimeKeeper* ptk = getTimeKeeper();
....
delete ptk;


這樣看來,以後我們定義一個類的時候,就把它的析構函數全部聲明為virtual函數,可以避免“局部銷毀”問題。 但是這更不是一個好主意。(PS: 感謝我的老師讓我知道了虛函數表這個東東.....) 還是先來看一個demo.
class Point {
public:
    Point(int xCoord, int yCoord);
    ~Point();
private:
    int x, y;
};

如果int占用32bits,那麼Point對象可塞入一個64bit緩存器中。這樣一個Point對象可被當作一個“64bit量”傳給以其他語言如C或Fortran撰寫的函數。
但是如果這裡的析構函數被聲明為virtual,會引起什麼影響呢? virtual關鍵字可以在運行期決定哪一個virtual函數被調用,這個強大的功能顯然要付出代價的。這個代價就是需要額外的空間存儲虛函數表——編譯器在其中尋找適當的函數指針,以及指向其中的指針(存儲在對象中)。(這裡不討論虛函數表的實現細節) 所以,如果將析構函數聲明為virtual,Point對象的體積就會增大:在32bit計算機體系結構中將占用64bits到96bits(加上虛函數指針32bits)。因此,添加一個虛函數會使得這個對象增大50%~100%。C++的該對象也就無法和C裡的該對象兼容了,如果不明確補償,那麼兩者就無法兼容了。
總結一句話就是:盲目地將所有類的析構函數聲明為virtual,或者non-virtual都是錯誤的。
需要格外注意的一點是:不要企圖繼承一個標准容器或者其他“帶有non-virtual析構函數”,雖然看起來很方便。就像下面做的這樣:
class SpecialString: public std::string {
    ......
};

// 如果你有一段代碼這樣寫,絕對是你悲劇的開始
SpecialString* pss = new SpecialString("Hello world!");
std::string* ps;
......
ps = pss;
......
delete ps;         // 局部銷毀,發生了資源洩漏


如果你確定這個類是當作一個父類來使用的話,聲明一個抽象類或許是一個不錯的主意。 來看一個demo
class AWOV {
public:
    virtual ~AWOV() = 0;
};
AWOV::~AWOV() {}     //純虛函數的定義

這裡有一個需要注意的地方是,這個析構函數的定義是必須的,不然編譯器會報錯。(因為編譯不會再為你默默的生成一個了)
小結: 帶有多態性質的父類應該聲明一個virtual析構函數,如果class帶有任何virtual函數,它就應該擁有一個virtual析構函數。如果一個類的不是設計為一個父類來使用,或不是為了具備多態性,就不應該聲明virtual析構函數,當然,不要有繼承它的類出現。



  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved