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
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
類繼承 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_ptr | shared_ptr |
|---|---|---|
| 對象大小 | 1 指針 | 2 指針 |
| 控制塊 | 無 | 動態分配(含引用計數、刪除器、分配器) |
| 解引用開銷 | 等同裸指針 | 等同裸指針 |
| 拷貝/移動 | 僅移動,極快 | 拷貝需原子增減;移動快 |
| 銷毀 | 直接 delete | 原子減計數,歸零時 delete + 虛函數調用刪除器 |
unique_ptr(零開銷抽象),需共享所有權時再「升級」為 shared_ptr(shared_ptr 可從 unique_ptr 構造)。shared_ptr 無法還原為 unique_ptr。shared_ptr 不支持數組(無 shared_ptr<T[]>),請用 vector/array 替代。shared_ptr 為共享所有權提供自動生命週期管理,控制塊包含原子引用計數、刪除器、分配器。unique_ptr:體積雙倍、有控制塊開銷、拷貝涉及原子操作。shared_ptr 類型(類型擦除),靈活性高。shared_ptr;需從 this 獲取時繼承 enable_shared_from_this 並用 shared_from_this()。