std::shared_ptr目前發現一些 實作定義 所存在的問題,由於不同編譯器的實作方法大不相同,因此這裏只指出部份編譯器可能存在的問題和坑以及解決方案,不討論語言標準本身的設定。至於這些問題是不是問題,見仁見智。
問題:
- 多執行緒環境下,構造析構的實作並非原子,只有參照計數是安全的,這在都使用std::shared_ptr時沒有問題,然而多執行緒環境由於異步任務瑣碎且分布於不同執行緒,出現迴圈參照的可能較大,std::weak_ptr就容易出現執行緒安全事故(修正一下,新的標準中已支持多緒安全的lock)。atomic_shared_ptr存在的主要問題則是CAS自旋鎖失敗後沒有及時讓出時間片,虛擬碼:
while
(
CAS
(...));
// Bad
while
(
CAS
(...)){
yield
();}
// Good
CAS放在一個死迴圈中是非常不好的實踐,對效能可能會造成淪陷性打擊,在高即時性要求的系統中,是不定時的效能炸彈。
2. std::shared_ptr邏輯可能全部在樣版中實作,而其本身程式碼量又很重,這會導致編譯速度被拖慢,且包體大小誇張。
3. 內部計數器需要多一個額外的記憶體分配,預設分配器下會造成記憶體碎片和效能問題。
4. 無法設定預設析構方法,不容易開洞:如計畫中為了跨動態可執行單元,可能會需要抽象解構函式為一個虛擬函式(詳見COM中Release的設定方法),因此我們可能需要智慧指標的deleter有一個預設的邏輯:
template<typename T>
void Deleter(T* ptr) {
if constexpr (std::is_base_of_v<IReleasable, T>) {
ptr->Release();
} else {
delete ptr;
}
}
雖然可以自己定義deleter,但這在多人協作計畫中遠不如預設方法方便。
解決方案:
- 在參照計數和析構模組,可使用自訂的自旋鎖實作,關於自旋鎖在不同平台的最佳實踐,Stack Overflow有大量文獻,方法雖然不一,但大體思路一致,都是盡可能讓排隊的執行緒不會疲於死迴圈。
- 對於智慧指標來說,指標的型別並不重要,只需要一個void*和一個解構函式的函式指標即可,因此智慧指標中重量級的邏輯都可以單獨實作並放到某個固定的編譯單元而非header,對於多繼承的型別轉換偏移可以直接記錄為整數,這就解決了編譯速度和包體問題。
- 承接第二條,既然邏輯寫入到獨立的編譯單元,就可以用一個靜態的分配池來分配大小和型別一致的計數器。
- 自己造輪子,開洞美滋滋。
最後附上自己的實作: