使用 C++ 有一個很惱人的問題,當 new 出一個多處共享,甚至在多執行緒共用的物件,使用這個物件時得擔憂是不是已被 delete 掉了,delete 時也得擔心是不是還有其他地方還在使用,或是已不使用了,有沒有被 delete = =? 這些瑣碎的事要是由人力處理,難保不出差錯。 理想中的的想法是當一個物件已沒被使用就自動 delete,但要如何知道已沒被使用了?這裡提供的方法是採用追蹤的方式,當一個物件找不到還有使用者就自動刪除。 擁有者與放棄擁有程式庫要求任何要具有自動刪除能力的物件,都必須是來自 cxxlObject 的延伸類別,cxxlObject 會自動記錄有誰擁有它,擁有者與被擁有者都須是 cxxlObjec 的延伸物件。 先從廣度的層面來看,假設 x 物件被 a、b、c 三個物件所擁有,表示 x 物件有三個擁有者,x 物件的存活取決於這三個擁有者是否全部放棄擁有,放棄擁有是這個機制很重要的概念。再從深度的層次來看,x 物件若還存在,必至少有一個擁有者存在,假設這個擁有者為 a1,而 a1 為什麼會存在,必至少有一個 a1 的擁有者 a2 存在,而 a2 的存在必至少有一個...,這樣下去要怎麼決定這些物件的存在? 根物件 rootObjectrootObject 是程式一開始就已建好的物件,他的任務只有一個,就是作為 x a1 a2 ... 一連串物件擁有者的最終擁有者。當一個物件發生被放棄擁有的情況發生時,並不是以廣度或深度上是否還有擁有者存在來決定這個物件的存在,而是在眾擁有者(廣度和深度)中是否找得到 rootObject 這個物件,注意!rootObject 是一個物件(當然它也是由 cxxlObject 繼承而來),而 cxxlObject 是一個類別,不要混淆了。 智慧指標 Smart_PtrSmart_Ptr 是作為 cxxlObject 和它的擁有者連結的工具,Smart_Ptr 和 cxxlObject 之間已建好了完善的自動銷毀機制,使用 Smart_Ptr 不用也不該再去擔心 delete 的工作。一個 cxxlObject 物件不一定要放入 Smart_Ptr,但 cxxlObject 物件交給 Smart_Ptr 後,就不可以再使用 delete 指令再去刪除這個物件,這點要記住。還有 cxxlObject 物件可以多次被放入 Smart_Ptr 中,cxxlObject 內含 CriticalSec 同步控制,但 Smart_Ptr 並沒有,因此 Smart_Ptr 不應共用,且使用 Smart_Ptr 的物件應做好同步控制。 Smart_Ptr 的建構函數宣如下:
類似 Smart_Ptr 這種用法的有 boost 的 shared_ptr,但 shared_ptr 採用 Countor 的做法,當遇到循環參照時,會造成 memory leak,使用 Smart_Ptr 不會有這種問題。 另一個比較會被質疑的是效率的問題,首先先瞭解一下 Smart_Ptr 處理銷毀的過程,當一個 cxxlObject 物件要被放棄擁有(可能是擁有者被銷毀或擁有者改用其他物件)時:
以上被稱為放棄擁有程序,有兩種 放棄擁有程序 可以被 cxxlObject 選用,一種稱為 Spirit_Easy,另一種稱為 Spirit_Urgent,Spirit_Urgent 會完整執行完 放棄擁有程序 才返回(即時);Spirit_Easy 只做完第一步驟就返回(非即時),其他就丟給一個負責處理的執行緒在背後處理。 顯然 Spirit_Easy 會快很多,但沒法確定物件會在何時會被銷毀,但採用 Spirit_Urgent 也得是最後一個擁有者放棄擁有才能看到銷毀效應,所以 Spirit_Urgent 比較適用於不是作為共享物件時使用,不過若不是要作為共享物件只要不放入 Smart_Ptr 就行了(得自行處理 delete),因此 Spirit_Urgent 的功用在一般使用上還看不出它的用途。
範例一
參數與回傳值對於已放入 Smart_Ptr 的 cxxlObjec 要作為參數傳遞有兩種方式
採用 F1 的方式會多一點點 Smart_Ptr 間的 assign 時間消耗,就採用 Spirit_Easy 的 cxxlObject 就是多了記錄和剔除 Smart_Ptr 的處理,而使用 F2 的方式可能會讓使用端忘了要把 cxxlObjec 先放入 Smart_Ptr 中,還有若這樣使用 F2(new type),若不被 Smart_Ptr 包裹會造 memory leek,折衷的方法是對外提供的功能採用 F1,對內採用 F2。 若是以下的用法呢:
F3 是傳遞和取值的用法,也就是 type_ref 是可以被更換包裹的物件,而 F4 我認為是代替 F1 的最好的解決用法,推薦採用這種方法來代替 F1 和 F2。 但不管用哪種方式,接收端若要保留 cxxlObjec 物件,一定要用 Smart_Ptr 包裹。 至於作為函數的回傳值以下方式是唯一的選擇
以下的回傳方式都有間題
F6 等於暴露了物件自己封裝的屬性,而 F7 沒辦法讓使用端知道這是一個 cxxlObject 物件。
同步控制cxxlObject 在生死存取的處理上是有同步控制,所以可允許多執行緒的多個 Smart_Ptr 對同一個 cxxlObject 處理使用。 但 Smart_Ptr 本身是沒有同步控制,所以 Smart_Ptr 本身不應該是可共享的,只可做物件內部的屬性變數,由擁有者的物件提供同步控制的保護。或者作為函數內的 local 變數。 所以像上面提到的 F4() 把 Smart_Ptr 本身傳遞出去必須要多加 const,以免被修改。而 F3() 最好只是作為取值回來使用就好 範例二延續範例一編寫一個使用 class A 的 class B
轉形智慧指標只包裝物件的一個介面,若要改用同一個物件的其他介面可用如下的方式
回傳值為指定介面的 Smart_Ptr<T>,若轉形失敗包裹的是 NULL,可用 Smart_Ptr<T>::isNULL() 做檢查
|
核心元件 >