| 嘗試 | 結果 | 原因 |
構造函數 private | 失敗 | 堆上 new 也無法呼叫私有構造函數 |
解構函數 private | 失敗 | 繼承類無法呼叫基類私有解構函數 → 無法繼承 |
解構函數 protected | 成功 | 棧上建構需編譯器生成隱式解構呼叫 → 存取失敗;堆上 delete 由類成員/友元呼叫 → 合法;繼承類可呼叫 → 支援繼承 |
經典實現:工廠模式 + 保護型解構函數
class HeapOnly {
protected:
HeapOnly() = default; // 保護構造:僅衍生類/友元可建構
~HeapOnly() = default; // 保護解構:關鍵點!
public:
// 靜態工廠方法:唯一合法建構入口
static HeapOnly* create() {
return new HeapOnly(); // 類內部可訪問 protected 構造
}
// 成員銷毀方法:唯一合法銷毀入口
void destroy() {
delete this; // 類內部可訪問 protected 解構
}
// 禁用拷貝/移動(可選,視需求)
HeapOnly(const HeapOnly&) = delete;
HeapOnly& operator=(const HeapOnly&) = delete;
};
// 使用方式
int main() {
// HeapOnly obj; // 編譯錯誤:棧上建構需呼叫 public 解構函數
// HeapOnly* p = new HeapOnly(); // 編譯錯誤:外部無法訪問 protected 構造
HeapOnly* p = HeapOnly::create(); // 正確:工廠建構
p->destroy(); // 正確:成員銷毀
return 0;
}
核心機制深度解析
為什麼棧上建構會失敗?
棧上物件離開作用域時,編譯器自動插入解構函數呼叫。此呼叫發生在使用者代碼(非類成員/友元),無法存取 protected 解構函數 → 編譯錯誤。
為什麼堆上 delete 能成功?
delete p 分兩步:1. 呼叫解構函數 2. 釋放記憶體。若 p 型別為 HeapOnly*,解構函數呼叫發生在類成員函數(如 destroy())或友元函數中,合法存取 protected 解構函數。
繼承支援
衍生類解構函數需呼叫基類解構函數。protected 允許衍生類存取,完美支援繼承體系。若為 private,衍生類無法呼叫 → 編譯錯誤。
現代 C++ 改良:搭配智能指標與自定義刪除器
class HeapOnly {
protected:
~HeapOnly() = default;
public:
static std::unique_ptr create() {
return {new HeapOnly(), [](HeapOnly* p){ p->destroy(); }};
}
void destroy() { delete this; }
};
// 使用:自動 RAII 管理
auto ptr = HeapOnly::create(); // 智能指標離開作用域自動呼叫 destroy()
應用場景與變體
- 單例模式:構造函數
private,靜態 getInstance() 返回靜態區物件(非堆)
- Reference Counting 基類:解構函數
protected,release() 內 delete this
- COM 介面:
AddRef/Release,Release 歸零時 delete this
- 異步任務物件:任務佇列持有智能指標,完成後自動銷毀
總結
protected 解構函數是「強制堆分配、禁止棧建構、保留繼承能力」的精準槓桿。配合工廠方法與 destroy()(或智能指標自定義刪除器),構成 C++ 資源管理的經典模式。理解其編譯期存取檢查機制,是掌握 C++ 物件生命週期控制的關鍵一課。
- 上一篇文章: C 語言文件讀寫四大模式深度對比:字元、字串、塊、格式化操作完整實戰指南
- 下一篇文章: Qt QTreeWidget 實現類 Eva 聯繫人列表:模型/視圖架構、項目自定義與資源系統完整實戰
Copyright ©
程式碼之家 All Rights Reserved