GotW #89智能指針的一些建議

question:
1.什麼時候應該使用shared_ptr vs. unique_ptr? 列出儘可能多的注意事項。
2.爲什麼你幾乎總是使用make_shared來創建一個由shared_ptrs擁有的對象? 說明。
3.爲什麼你幾乎總是使用make_unique創建一個對象,最初由unique_ptr擁有? 說明。

4.與auto_ptr有什麼關係?

solution:

1.什麼時候應該使用shared_ptr vs. unique_ptr?
當有疑問時,默認情況下優先使用unique_ptr,如果需要,您可以隨後將其移動轉換爲shared_ptr。如果你從一開始就知道需要共享所有權,那麼通過make_shared直接訪問shared_ptr(參見下面的#2)。
有三個主要原因說“當有疑問,喜歡unique_ptr”。
首先,使用最簡單的語義是足夠的:選擇正確的智能指針,以最直接表達你的意圖,和你需要什麼(現在)。如果您要創建一個新對象,但不知道您最終需要共享所有權,請使用表示唯一所有權的unique_ptr。你仍然可以把它放在一個容器(例如,向量<unique_ptr <widget >>),並做大多數其他事情,你想做一個原始指針,只有安全。如果以後需要共享所有權,則可以將unique_ptr總是移動轉換爲shared_ptr。
第二,unique_ptr比shared_ptr更有效。 unique_ptr不需要維護引用計數信息和覆蓋下的控制塊,並且被設計爲移動和使用作爲原始指針很便宜。當你不要求超過你需要,你不會招致你不會使用的開銷。
第三,從unique_ptr開始更靈活,並保持您的選擇打開。如果你以一個unique_ptr開頭,你可以隨後通過移動轉換爲shared_ptr,或者通過.get()或.release()轉換爲另一個自定義的智能指針(甚至轉換爲原始指針)。
指南:希望使用標準智能指針,默認爲unique_ptr,如果需要共享,則使用shared_ptr。它們是所有C ++庫都能理解的常見類型。僅在需要與其他庫的互操作性時使用其他智能指針類型,或者在標準指針上使用刪除器和分配器無法實現的自定義行爲的必要時使用。

2.爲什麼你幾乎總是使用make_shared來創建一個由shared_ptrs擁有的對象?說明。
注意:如果需要使用自定義分配器創建對象,這很少見,您可以使用allocate_shared。注意,即使它的名稱稍有不同,alloc_shared應該被視爲“只是make_shared的風味,讓你指定一個分配器,”所以我主要是談論他們作爲make_shared這裏,而不是區別很大。

有兩種主要情況,您不能使用make_shared(或allocate_shared)創建一個您知道將由shared_ptrs擁有的對象:(a)如果您需要自定義刪除器,例如因爲使用shared_ptrs來管理非內存資源或在非標準內存區域中分配的對象,則不能使用make_shared,因爲它不支持指定刪除程序;和(b)如果你採用一個原始指針指向從其他(通常是遺留)代碼交給你的對象,你將直接從那個原始指針構造一個shared_ptr。

指南:使用make_shared(或者,如果你需要一個自定義分配器,allocate_shared)來創建一個對象,你知道它將由shared_ptrs擁有,除非你需要一個自定義刪除器或者從其他地方採用一個原始指針。

所以,爲什麼使用make_shared(或者,如果你需要一個自定義分配器,allocate_shared)只要你可以,這幾乎總是?有兩個主要原因:簡單性和效率。

首先,使用make_shared代碼更簡單。爲了清楚和正確,首先寫。

第二,使用make_shared更有效率。 shared_ptr實現必須在由引用給定對象的所有shared_ptrs和weak_ptrs共享的控制塊中維護內務處理信息。特別地,該內務信息必須不僅包括一個而且包括兩個引用計數:

“強引用”計數跟蹤當前保持對象活動的shared_ptrs的數量。 當最後的強引用消失時,共享對象被銷燬(並且可能被釋放)。“弱引用”計數來跟蹤當前觀察對象的weak_ptrs的數目。 當最後的弱引用消失時,共享內務控制塊被銷燬和釋放(並且共享對象被釋放,如果它還沒有)。如果您通過原始新表達式單獨分配對象,然後將其傳遞給shared_ptr,則shared_ptr實現不能單獨分配控制塊,如示例2(a)和圖2(a)所示。


// Example 2(a): Separate allocation
auto sp1 = shared_ptr<widget>{ new widget{} };
auto sp2 = sp1;

Figure 2(a): Approximate memory layout for Example 2(a).

We’d like to avoid doing two separate allocations here. If you use make_shared to allocate the object and the shared_ptr all in one go, then the implementation can fold them together in a single allocation, as shown in Example 2(b) and Figure 2(b).

// Example 2(b): Single allocation
auto sp1 = make_shared<widget>();
auto sp2 = sp1;

Figure 2(b): Approximate memory layout for Example 2(b).



注意,組合分配有兩個主要優點:
它減少了分配開銷,包括內存碎片。首先,最明顯的做法是減少分配請求的數量,這通常是更昂貴的操作。這也有助於減少對分配器的爭用(一些分配器不能很好地擴展)。第二,僅使用一個內存塊而不是兩個內存減少了每個分配的開銷。每當你請求一塊內存,系統必須給你至少這麼多字節,並且通常給你一些,因爲使用固定大小的池或者每個分配的內務信息。因此,通過使用單個內存塊,我們傾向於減少總額外開銷。最後,我們還自然地減少導致碎片的間隙之間的“死”額外的數量。
它提高了地方性。引用計數經常與對象一起使用,並且對於小對象可能在同一緩存行上,這提高了緩存性能(只要沒有一些線程在緊密循環中複製智能指針;不要去做)。
和往常一樣,當你可以表達更多的你想要實現的一個單一的函數調用,你給系統一個更好的機會,找出一種方法來更有效地工作。當使用對v.insert(第一個,last)的單個範圍插入調用而不是對v.insert(value)的100個調用將100個元素插入向量時,這是真的,因爲它使用單個調用make_shared調用新的widget()和shared_ptr(widget *)。

還有兩個優點:使用make_shared避免顯式新,並避免異常安全問題。這兩個也適用於make_unique,所以我們將在#3下覆蓋它們


3.爲什麼你幾乎總是使用make_unique創建一個對象,最初由unique_ptr擁有?說明。
和make_shared一樣,有兩種情況,你不能使用make_unique來創建一個對象,你知道這個對象將被unique_ptr擁有(至少在最初):如果你需要一個自定義的刪除器,或者你正在採用一個原始指針。
否則,這幾乎總是,更喜歡make_unique。
指南:使用make_unique創建一個未共享的對象(至少尚未共享),除非您需要自定義刪除程序或從其他地方採用原始指針。
除了具有make_shared的對稱性之外,make_unique還提供至少兩個其他優點。首先,你應該更喜歡使用make_unique <T>()而不是更冗長的unique_ptr <T> {new T {}},因爲你應該避免一般的顯式new:
指南:不要使用顯式的新,刪除和擁有*指針,除非在極少數情況下封裝在低級數據結構的實現中。
第二,它避免了裸體新的一些已知的異常安全問題。這裏有一個例子:

void sink( unique_ptr<widget>, unique_ptr<gadget> );

sink( unique_ptr<widget>{new widget{}},
      unique_ptr<gadget>{new gadget{}} ); // Q1: do you see the problem?
簡單來說,如果你首先分配和構造新的小部件,然後在分配或構造新的小部件時獲得異常,該小部件被泄漏。 你可能會想:“嗯,我可以只是改變新的小部件{}到make_unique <widget>(),這個問題會消失,對吧?

sink( make_unique<widget>(),
      unique_ptr<gadget>{new gadget{}} );         // Q2: is this better?
答案是否定的,因爲C ++留下未指定的函數參數的評估順序,因此可以首先執行新的小部件或新的小部件。 如果新的小工具首先被分配和構造,那麼make_unique <widget>拋出,我們有同樣的問題。
但是,雖然只是更改其中一個參數使用make_unique不關閉洞,將它們改爲make_unique真的完全消除了問題:
sink( make_unique<widget>(), make_unique<gadget>() );  // exception-safe
這個異常安全問題在GotW#56中有更詳細的介紹。
指南:要分配一個對象,更喜歡默認編寫make_unique,當你知道對象的生命週期將被使用shared_ptrs來管理時,寫make_shared。

4.與auto_ptr有什麼關係?
auto_ptr最有趣的特點是在C ++移動語義之前嘗試創建一個unique_ptr。 auto_ptr現在已被棄用,不應在新代碼中使用。
如果你在現有的代碼庫中有auto_ptr,當你有機會嘗試全局搜索和替換auto_ptr到unique_ptr; 絕大多數的使用都是一樣的,它可能暴露(作爲編譯時錯誤)或修復(默默)一個或兩個你不知道你有。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章