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

Effective Modern C++ 條款 19:用 shared_ptr 管理共享所有權資源

     編輯:關於C++

std::shared_ptr 實現了「垃圾回收器般的自動生命週期管理」與「析構函數般的確定性銷毀」的完美結合。本條款深入剖析其控制塊機制、線程安全引用計數、自定義刪除器/分配器設計、enable_shared_from_this 解決 this 指針管理難題,以及與 unique_ptr 的性能權衡。

一、核心機制:控制塊與引用計數

  • 每個被管理對象關聯一個動態分配的控制塊,包含:強引用計數、弱引用計數、刪除器、分配器。
  • shared_ptr 大小 = 2 個指針(對象指針 + 控制塊指針)。
  • 引用計數增減為原子操作,保證線程安全,但比非原子操作慢。
  • 最後一個 shared_ptr 銷毀時(強引用計數歸零)調用刪除器銷毀對象;控制塊在最後一個 weak_ptr 也銷毀時才釋放。

二、創建與拷貝/移動語義

auto sp1 = std::make_shared<Widget>(); // 推薦:單次分配,無裸指針
auto sp2 = sp1;                          // 拷貝:強引用計數 +1 (原子)
auto sp3 = std::move(sp1);               // 移動:指針轉移,計數不變,sp1 置空
// 移動比拷貝快:無原子增減開銷

三、自定義刪除器:類型擦除設計

與 unique_ptr 不同,shared_ptr 的刪除器類型不影響智能指針類型(類型擦除存儲於控制塊),因此:

  • 不同刪除器的 shared_ptr<Widget> 類型相同,可放入同一容器、相互賦值、傳遞給同一函數。
  • shared_ptr 對象大小固定(2 指針),刪除器內存另行分配於控制塊。
auto del1 = [](Widget* p){ log(p); delete p; };
auto del2 = [](Widget* p){ audit(p); delete p; };
std::shared_ptr<Widget> p1(new Widget, del1);
std::shared_ptr<Widget> p2(new Widget, del2); // 類型相同
std::vector<std::shared_ptr<Widget>> vec = {p1, p2}; // OK

四、避免多重控制塊:禁止用裸指針構造多個 shared_ptr

Widget* pw = new Widget;
std::shared_ptr<Widget> sp1(pw); // 控制塊 A
std::shared_ptr<Widget> sp2(pw); // 控制塊 B → 雙重 delete → UB

// 正確做法:
auto sp1 = std::make_shared<Widget>();
std::shared_ptr<Widget> sp2 = sp1; // 共享控制塊 A

五、enable_shared_from_this:安全從 this 獲取 shared_ptr

類繼承 std::enable_shared_from_this<T>,成員函數調用 shared_from_this() 返回共享當前對象的 shared_ptr,複用現有控制塊。

class Widget : public std::enable_shared_from_this<Widget> {
public:
    void Process() {
        processedWidgets.push_back(shared_from_this()); // 安全
    }
private:
    static std::vector<std::shared_ptr<Widget>> processedWidgets;
};

// 必須通過 shared_ptr 管理 Widget,否則 shared_from_this 行為未定義
auto pw = std::make_shared<Widget>();
pw->Process();

最佳實踐:構造函數私有化,提供工廠函數返回 shared_ptr,強制正確使用。

六、性能開銷與選型建議

指標unique_ptrshared_ptr
對象大小1 指針2 指針
控制塊無動態分配(含引用計數、刪除器、分配器)
解引用開銷等同裸指針等同裸指針
拷貝/移動僅移動,極快拷貝需原子增減;移動快
銷毀直接 delete原子減計數,歸零時 delete + 虛函數調用刪除器
  • 首選 unique_ptr(零開銷抽象),需共享所有權時再「升級」為 shared_ptr(shared_ptr 可從 unique_ptr 構造)。
  • 反向轉換不可行:shared_ptr 無法還原為 unique_ptr。
  • shared_ptr 不支持數組(無 shared_ptr<T[]>),請用 vector/array 替代。

七、總結:必記四點

  1. shared_ptr 為共享所有權提供自動生命週期管理,控制塊包含原子引用計數、刪除器、分配器。
  2. 相比 unique_ptr:體積雙倍、有控制塊開銷、拷貝涉及原子操作。
  3. 刪除器類型不影響 shared_ptr 類型(類型擦除),靈活性高。
  4. 嚴禁用同一裸指針構造多個 shared_ptr;需從 this 獲取時繼承 enable_shared_from_this 並用 shared_from_this()。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved