《Linux 多執行緒伺服端程式設計》書中指出:shared_ptr 參考計數本身無鎖安全,但對象讀寫非安全,因有兩個資料成員,讀寫無法原子化。線程安全級別同內建類型/標準容器:多線程可同時讀同一物件、可同時寫不同物件、同一物件多線程讀寫必須加鎖。本文深入分析為何「兩個資料成員導致非原子」會造成災難性後果。
shared_ptr<Foo> 含兩成員:
ptr:指向 Foo 物件ref_count:指向堆上控制塊(含引用計數、弱引用計數、deleter、allocator)圖示:
shared_ptr x(new Foo); // x.ptr → Foo, x.ref_count → ControlBlock(count=1) shared_ptr y = x; // 複製兩個指標,計數變 2
y = x 分兩步驟(順序視實作而定,通常先 ptr 後 ref_count):
ptr 指標ref_count 指標 → 控制塊計數 +1三個 shared_ptr:g (共享)、x (線程 A 局部)、n (線程 B 局部)
// 初始狀態 g → Foo1 (count=1) x → 空 n → Foo2 (count=1) // 線程 A: x = g (讀 g) - 完成步驟1 (複製 ptr),尚未步驟2 // 切換到線程 B: g = n (寫 g) - 完成兩步驟 // 步驟1: g.ptr = n.ptr (指向 Foo2) // 步驟2: g.ref_count = n.ref_count, Foo1 計數歸零 → Foo1 被銷毀 // 切回線程 A: 完成步驟2 (複製舊 ref_count,計數+1) // 結果:x.ptr 指向已銷毀的 Foo1 → 懸空指標!
若 operator= 實作順序相反(先 ref_count 後 ptr),同樣存在競態:線程 A 複製 ref_count 後、複製 ptr 前,線程 B 修改 g 導致控制塊計數錯誤或物件提前銷毀。
hash_value 為隱式轉 bool 結果,導致退化為鏈表boost/functional/hash/extensions.hpp 即安全高效idleconnection 範例自訂 hash_value 因舊版 Boost 存此 bug支援三大進階功能:
shared_ptr<Derived> 轉 shared_ptr<Base>,控制塊記住 Derived 類型確保正確銷毀delete 控制塊記錄的原始指標而非 void*shared_ptr<Base>.ptr 與控制塊 ptr 不同,銷毀時仍正確單次記憶體配置同時容納物件與控制塊,省一次配置開銷。資料結構:
[ ControlBlock | Foo Object ] // 連續記憶體區塊
需 C++11 perfect forwarding 完美轉發建構參數。