背景:C++ 手動內存管理痛點
C++ 要求 `new`/`delete` 配對,複雜對象圖中易發生內存洩漏、雙重釋放、懸空指針。Java/C# 垃圾回收器自動管理,但 C++ 作為編譯型語言無運行時 GC,需庫級實現智能指針。
第一版:簡單引用計數 auto_ptr
- 全局 `CPtrManager` 維護 `void* → (refCount, mark)` 映射
- 重載全局 `operator new/delete` 註冊/註銷分配塊
- `auto_ptr` 構造/拷貝/賦值/析構時增減引用計數,歸零時 `delete`
缺陷:無法處理循環引用(A→B, B→A 計數永不歸零)、非線程安全。
循環引用(互鎖)檢測與解鎖算法
- 識別堆上智能指針:`CPtrManager::GetAutoMark(ptr)` 通過地址範圍判斷指針對象是否位於某個受管理堆塊內
- 構建候選集:收集所有「引用計數>0 且所有持有者均在堆上」的塊(即無棧上根引用)
- 去除偽循環:迭代刪除持有者包含非候選集指針的塊,剩下的為真循環組
- 分組釋放:對每個強連通分量調用 `Release()` 遞歸釋放
- 後台線程定期掃描:`CMarkTable::DoLockMark()` 在單獨線程執行,持有 `CPtrManager` 寫鎖,避免干擾主線程
線程安全增強
- `CPtr` 內部 `CMutex` 保護單塊操作
- `CPtrManager` 全局 `CMutex` 保護映射表
- 掃描階段 `bWrite` 標志獨佔寫入,讀操作繼續(讀寫分離可進一步優化)
現代 C++ 最佳實踐:標準庫智能指針
C++11 引入標準智能指針,**生產代碼首選標準庫**,手寫僅教學/特殊場景:
| 類型 | 所有權語義 | 適用場景 | 循環引用解決 |
| `std::unique_ptr` | 獨占,不可拷貝可移動 | 單一所有者、RAII 資源管理 | 無循環(無共享) |
| `std::shared_ptr` | 共享,引用計數(控制塊原子操作) | 多所有者、緩存、觀察者 | `std::weak_ptr` 打破循環 |
| `std::weak_ptr` | 非所有權觀察者,不增加計數 | 緩存、父子雙向引用、打破循環 | 配合 `shared_ptr` 使用 |
shared_ptr + weak_ptr 破循環示例
struct Node {
std::shared_ptr next;
std::weak_ptr prev; // 弱引用,不增加計數
~Node() { std::cout << "Node destroyed\n"; }
};
void test() {
auto a = std::make_shared();
auto b = std::make_shared();
a->next = b;
b->prev = a; // 弱引用
// 作用域結束,a/b 計數歸零,正常析構
}
自定義刪除器與數組支持
// 文件句柄 RAII
auto fp = std::shared_ptr(fopen("data.txt","r"), fclose);
// 數組
auto arr = std::unique_ptr(new int[100]);
arr[5] = 42;
性能與避坑提示
- `make_shared` 單次分配對象+控制塊,比 `new` + `shared_ptr` 構造快、內存局部性好
- 避免循環引用:設計時明確所有權方向,必要處用 `weak_ptr`
- 多線程讀寫 `shared_ptr` 控制塊原子操作無鎖,**但指向對象的訪問需自行同步**
- 不要混用裸指針與智能指針管理同一對象
- 自定義 `deleter` 類型會成為 `shared_ptr` 類型一部分,影響容器存儲
結語
手寫 GC 智能指針展示了引用計數、循環檢測、線程同步核心機制,但現代 C++ 開發應直接使用標準庫 `unique_ptr`/`shared_ptr`/`weak_ptr` 組合,配合 RAII 原則,實現安全、高效、無 GC 停頓的確定性內存管理。
- 上一篇文章: Java 字符編碼全景解析:從基礎概念到 URL/數據庫/前端實戰
- 下一篇文章: SCA Java 客戶端與實現模型詳解
Copyright ©
程式碼之家 All Rights Reserved