Effective C++ 條款07:為多態基類聲明virtual析構函數
核心思想:當通過基類指針刪除派生類對象時,如果基類沒有虛析構函數,會導致派生類資源泄漏。因為此時只會調用基類的析構函數,而不會調用派生類的析構函數。
?? 1. 問題場景:非虛析構函數導致資源泄漏
class Base {
public:Base() { std::cout << "Base構造\n"; }~Base() { std::cout << "Base析構\n"; } // 非虛析構函數
};class Derived : public Base {
public:Derived() : data(new int(42)) { std::cout << "Derived構造\n"; }~Derived() { delete data; // 釋放資源std::cout << "Derived析構\n"; }
private:int* data; // 派生類獨占資源
};int main() {Base* pb = new Derived(); // 基類指針指向派生類對象delete pb; // 僅調用Base::~Base() → 內存泄漏!
}
輸出結果:
Base構造
Derived構造
Base析構
問題:Derived
的資源data
未被釋放 → 內存泄漏!
? 2. 解決方案:聲明虛析構函數
class Base {
public:Base() { std::cout << "Base構造\n"; }virtual ~Base() { std::cout << "Base析構\n"; } // 虛析構函數
};class Derived : public Base { /* 實現同上 */ };int main() {Base* pb = new Derived();delete pb; // 正確調用派生類析構函數
}
輸出結果:
Base構造
Derived構造
Derived析構 // 先調用派生類析構函數
Base析構 // 再調用基類析構函數
🔍 3. 關鍵原則
場景 | 析構函數要求 | 原因 |
---|---|---|
多態基類(有虛函數) | 必須為virtual | 確保通過基類指針刪除派生類對象時,正確調用派生類析構函數 |
非多態基類(無虛函數) | 不應為virtual | 避免虛表指針帶來的空間開銷(條款7指出每個對象增加4-8字節) |
STL容器(如std::string ) | 禁止繼承 | 標準庫類的析構函數均為非虛,通過基類指針刪除派生類對象會導致未定義行為 |
?? 4. 錯誤實踐:繼承STL容器類
class MyString : public std::string {
public:~MyString() { std::cout << "MyString析構\n"; }
};int main() {std::string* ps = new MyString(); delete ps; // 未定義行為!std::~string非虛
}
結果:MyString::~MyString()
不會被調用 → 潛在資源泄漏!
💎 5. 純虛析構函數的特殊用法
使類成為抽象類,同時仍需要提供實現
class AbstractBase {
public:virtual ~AbstractBase() = 0; // 純虛聲明
};
AbstractBase::~AbstractBase() {} // 必須提供實現class Concrete : public AbstractBase {
public:~Concrete() override { std::cout << "Concrete析構\n"; }
};int main() {AbstractBase* p = new Concrete();delete p; // 正確調用鏈:Concrete::~ → AbstractBase::~
}
總結:多態基類虛析構三原則
- 多態基類必須聲明虛析構函數
virtual ~Base() = default;
- 非多態基類不要聲明虛析構函數
避免無謂的虛函數表開銷 - 禁止繼承無虛析構函數的類(如STL容器)
組合優于繼承:將目標類作為成員變量而非基類