解構函式是建構函式的「逆向操作」,在物件銷毀時自動呼叫。命名規則:類別名稱前加 ~,如 ~String()。
| 場景 | 說明 | 範例 |
|---|---|---|
| 1. delete 釋放 | new 配置的物件被 delete | Obj* p = new Obj; delete p; |
| 2. 離開作用域 | 區域/自動物件離開 {} 範圍 | 函式區域變數、區塊內變數 |
| 3. 暫時物件結束 | 臨時物件生命週期結束 | func(Obj()); 參數傳值產生的暫時物件 |
| 4. 程式結束 | 全域/靜態物件在 main 結束時 | 全域變數、static 區域變數 |
| 5. 顯式呼叫 | 使用完整限定名稱呼叫 | obj.~ClassName(); (極少見,配合 placement new) |
void 都不能寫class Base {
public:
virtual ~Base() { cout << "Base 解構\n"; }
};
class Derived : public Base {
public:
~Derived() { cout << "Derived 解構\n"; }
};
int main() {
Base* p = new Derived();
delete p; // 虛擬解構確保輸出:Derived 解構 → Base 解構
return 0;
}
若 Base 解構函式非 virtual,delete p 僅呼叫 ~Base(),導致 Derived 資源洩漏。
class Base { ~Base() { cout << "~Base\n"; } };
class Member { ~Member() { cout << "~Member\n"; } };
class Derived : public Base {
Member m;
~Derived() { cout << "~Derived\n"; }
};
// 建構順序:Base → Member → Derived
// 解構順序:Derived → Member → Base (完全相反)
int* badFunc() {
int arr[10];
return arr; // 錯誤:arr 在函式結束時銷毀,回傳野指標
}
// 錯誤模式 delete p; delete p; // 雙重釋放 // 或 delete p; *p = 10; // 使用已釋放記憶體 (野指標) // 最佳實務:釋放後立即歸零 delete p; p = nullptr;
透過基類指標管理衍生物件時,必須將基類解構函式宣告為 virtual。
資源取得即初始化,利用物件生命週期自動管理資源:
class FileHandle {
FILE* fp;
public:
FileHandle(const char* name, const char* mode) : fp(fopen(name, mode)) {}
~FileHandle() { if (fp) fclose(fp); } // 保證釋放
// 禁止複製,支援移動...
};
void func() {
FileHandle f("data.txt", "r"); // 離開作用域自動 fclose
// 即使拋出例外也會正確釋放
}
| 智慧指標 | 適用場景 | 解構行為 |
|---|---|---|
unique_ptr | 獨佔所有權 | 自動 delete |
shared_ptr | 共享所有權 | 引用計數歸零時 delete |
weak_ptr | 觀察者、打破循環參考 | 不影響生命週期 |