當通過基類指針刪除派生類對象時,若基類析構函數非虛函數,則只會調用基類析構函數,導致派生類資源洩漏。聲明為 virtual 後,delete 會觸發動態綁定,依次調用派生類與基類析構函數,實現多態鏈式銷毀。
class Base { public: virtual ~Base() {} };
class Derived : public Base { public: ~Derived() {} };
Base *pb = new Derived;
delete pb; // 正確調用 ~Derived() 再 ~Base()
將析構函數聲明為純虛函數(virtual ~Base() = 0;)可使類成為抽象類,禁止實例化。但必須提供函數體,否則鏈接階段報錯「unresolved external symbol」。
原因:派生類析構函數隱含調用基類析構函數,編譯器需要基類析構函數的實際代碼地址。純虛僅表示「派生類必須重寫」,不代表「無定義」。
class Base {
public:
virtual ~Base() = 0; // 聲明純虛
};
Base::~Base() {} // 必須在類外提供定義(不能內聯寫在類內)
純虛函數(=0)的語義是「強制派生類實現」,而非「無實現」。基類仍可提供默認實現供派生類調用:
class Base {
public:
virtual void Hierarchy() const = 0;
};
void Base::Hierarchy() const { cout << "Base::Hierarchy"; } // 提供默認實現
class Derived : public Base {
public:
void Hierarchy() const override {
Base::Hierarchy(); // 顯式調用基類實現
cout << "Derived::Hierarchy";
}
};
| 問題 | 結論 |
|---|---|
| 構造函數能否為虛函數? | 不能。構造時對象尚未完全形成,虛表指針未就緒。 |
| 構造函數中調用虛函數會怎樣? | 執行基類版本(多態被禁用),因派生類部分未構造。 |
| 析構函數可以是純虛嗎? | 可以,但必須提供定義體。 |
| 純虛函數是否一定無函數體? | 不,可以有函數體,甚至可被顯式調用。 |
| override 時返回類型能否協變? | 僅當返回類型為指針/引用且指向派生類時允許(如 Clone 模式)。 |
Stroustrup 指出:純虛函數更靈活,允許分階段定義類。中間層類只需關注自身職責,無需知道是否已實現所有祖先的純虛函數,也無需在類級別聲明 abstract。這對複雜繼承體系尤為重要。
override 關鍵字,編譯器將檢查簽名一致性,防止隱藏而非覆蓋。