首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

C++ 智慧指標進階:shared_ptr 循環引用與自定義刪除器深度解析

     編輯:關於C++

shared_ptr 循環引用問題

shared_ptr 採引用計數管理物件生命週期。當兩個物件互相持有對方的 shared_ptr 時,計數永不歸零,導致記憶體洩漏。

問題重現

struct Node {
    int data;
    shared_ptr<Node> next;
    shared_ptr<Node> prev;  // 雙向連結
};

void testCycle() {
    auto n1 = make_shared<Node>();
    auto n2 = make_shared<Node>();
    cout << n1.use_count() << ' ' << n2.use_count() << endl;  // 1 1

    n1->next = n2;
    n2->prev = n1;
    cout << n1.use_count() << ' ' << n2.use_count() << endl;  // 2 2
}  // 離開作用域,n1/n2 計數 2→1,物件未釋放!

循環引用示意圖:兩物件互指,引用計數卡在 2

解決方案:weak_ptr 弱引用

weak_ptr 不增加引用計數,專為打破循環設計。需配合 shared_ptr 使用,無法單獨存取物件。

修正版 Node

struct Node {
    int data;
    shared_ptr<Node> next;      // 擁有權:下一節點
    weak_ptr<Node> prev;        // 非擁有權:上一節點
};

使用 weak_ptr

void useWeak() {
    auto n1 = make_shared<Node>();
    auto n2 = make_shared<Node>();
    n1->next = n2;
    n2->prev = n1;  // weak_ptr 不增加計數

    // 存取 weak_ptr 需轉為 shared_ptr (lock)
    if (auto p = n2->prev.lock()) {
        cout << "prev data: " << p->data << endl;
    }
}  // 正常釋放,計數歸零

自定義刪除器

shared_ptr 預設用 delete 釋放。管理非 new 分配資源(檔案控制代碼、C 風格陣列、malloc 記憶體、Windows HANDLE、OpenGL 物件等)時,需提供自定義刪除器。

刪除器類型

可呼叫物件:函數指標、Lambda、仿函數 (functor)、std::function。

Lambda 範例

// 管理 FILE*
auto fp = shared_ptr<FILE>(fopen("data.txt", "w"), [](FILE* f){ fclose(f); });

// 管理 malloc 記憶體
auto buf = shared_ptr<void>(malloc(1024), [](void* p){ free(p); });

// 管理 C 風格陣列 (需配合 delete[])
auto arr = shared_ptr<int>(new int[10], [](int* p){ delete[] p; });

仿函數實作完整模擬

// 預設刪除器
struct DefaultDelete {
    template <typename T>
    void operator()(T* p) const { delete p; }
};

// 檔案關閉刪除器
struct Fclose {
    void operator()(FILE* f) const { if (f) fclose(f); }
};

// free 刪除器
struct Free {
    void operator()(void* p) const { free(p); }
};

// 簡易 shared_ptr 模擬 (教學用)
template <typename T, typename Deleter = DefaultDelete>
class SharedPtr {
    T* ptr;
    int* count;
    Deleter del;
public:
    SharedPtr(T* p = nullptr) : ptr(p), count(new int(1)), del() {}
    ~SharedPtr() { if (--*count == 0) { del(ptr); delete count; } }
    // 複製/移動/指派... 略
};

// 使用
SharedPtr<FILE, Fclose> fp(fopen("log.txt", "w"));  // 離開作用域自動 fclose
SharedPtr<int, Free> buf((int*)malloc(100));        // 離開作用域自動 free

最佳實踐總結

  1. 樹狀/單向結構用 shared_ptr,**雙向/父子雙向**必須一端改 weak_ptr。
  2. make_shared 優於 new:單次配置控制區塊+物件、例外安全。
  3. 自定義刪除器型別為模板參數一部分,不同刪除器型別的 shared_ptr 不可互指派。
  4. 避免 shared_ptr<T[]> (C++17 支援),舊標準用 shared_ptr<T>(new T[n], [](T* p){delete[] p;})。
  5. weak_ptr 觀察者模式、快取、打破循環三大用途。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved