有以下問題展開
- 析構函數要不要定義成虛函數?
- 基類的析構函數要不要定義成虛函數?
- 如果不定義會有什么問題,定義了在什么場景下起作用。
?
1. 基類析構函數何時必須定義為虛函數?
?當且僅當通過基類指針(或引用)刪除派生類對象時,基類的析構函數必須是虛函數。
class Base {
public:virtual ~Base() { cout << "Base destructor" << endl; } // 必須為虛函數
};class Derived : public Base {
private:int* data;
public:Derived() { data = new int[10]; }~Derived() override { delete[] data; cout << "Derived destructor" << endl; }
};// 關鍵代碼:
Base* ptr = new Derived(); // 基類指針指向派生類對象
delete ptr; // 如果Base::~Base()不是虛函數,則只調用Base的析構函數
2. 若基類析構函數不是虛函數,會發生什么?
- 內存泄漏:當通過基類指針刪除派生類對象時,只會調用基類的析構函數,而派生類的析構函數不會被調用。例如:
class Base {
public:~Base() { cout << "Base::~Base()" << endl; } // 非虛析構函數
};class Derived : public Base {
private:int* data;
public:Derived() { data = new int[10]; }~Derived() { delete[] data; // 資源釋放代碼cout << "Derived::~Derived()" << endl; }
};Base* ptr = new Derived();
delete ptr; // 只調用Base::~Base(),Derived的析構函數未被調用,data內存泄漏!
-
安全準則:
若一個類可能作為基類,且存在通過基類指針刪除派生類對象的場景,必須將基類析構函數定義為虛函數。
3. 虛析構函數的作用機制?
多態調用:虛析構函數會觸發動態綁定(運行時多態)。當通過基類指針刪除對象時,C++ 會根據指針實際指向的對象類型(而非指針類型)來決定調用哪個析構函數。
Base* ptr = new Derived();
delete ptr; // 實際調用Derived::~Derived(),再調用Base::~Base()
-
調用順序:
派生類析構函數自動調用基類析構函數(無論基類析構函數是否為虛函數),但只有虛析構函數能確保派生類析構函數被先調用。
?4. 何時不需要虛析構函數?
不作為基類的類:若一個類不打算被繼承(如final
類),其析構函數無需為虛函數。
class NonInheritable final {
public:~NonInheritable() { /* ... */ } // 無需為虛
};
不通過基類指針刪除對象:
若基類僅用于繼承接口而非管理資源(即不涉及刪除delete basePtr
),析構函數可以不是虛函數。
class Interface {
public:virtual void doSomething() = 0;~Interface() { /* 非虛,因為不通過Interface*刪除對象 */ }
};
純虛析構函數:可將基類析構函數聲明為純虛函數,但必須提供定義:
class Base {
public:virtual ~Base() = 0; // 純虛析構函數
};
Base::~Base() {} // 必須提供定義
場景 | 基類析構函數是否需為虛函數? |
---|---|
通過基類指針刪除派生類對象 | 必須為虛函數 |
類不打算被繼承 | 無需為虛函數 |
類作為基類但不通過基類指針刪除對象 | 無需為虛函數,但建議為虛以避免誤用 |
核心原則:若基類有虛函數或可能被繼承,永遠將其析構函數定義為虛函數。?這是防止內存泄漏的重要實踐。?