#Charpter4 智能指針
詩人和作曲家喜歡寫一些關于love的作品,也有可能寫一些關于計數(counting)的作品,很少有兩者兼顧的。總有些例外,如Elizabeth Barrett Browning:"How do I love thee? Let me count the ways",又如Paul Simon:"There must be 50 ways to leave your lover.",被這些詩句啟發,我們來嘗試列舉下為什么原生指針(raw pointer)不那么討人喜歡(love)的理由:
1.從它的聲明看不出它指向的是一個單個的對象還是一個數組
2.當你使用完它的時候,從它的聲明看不出來你是否應該把它銷毀,例如,當指針擁有(owns)它當前指向的對象時
3.當你確定要銷毀它指向的內容的時候,又要犯難了,因為你不知道要使用delete,還是要使用另外一個不同的銷毀機制(如將該指針傳遞到一個指定的析構函數里)
4.當你終于要使用delete決定要銷毀它了,因為第1條,你又不知道該使用delete還是delete[],因為一旦使用錯誤,結果會是不確定的
5.最后,你終于確定了指針指向的內容是啥了,也確定了改用什么樣的方式來銷毀;問題又來了,因為你不能保證在你的程序的每條路徑中,你的銷毀代碼只執行一次,不執行的話會造成內存泄露,多執行哪怕一次會產生不確定的行為
6.目前沒有方法來確定一個指針是懸掛指針,即確定一個指針不再擁有它指向的對象。當一個指針指向的對象被銷毀了,該指針就變成了懸掛指針。
原生指針是一款很強大的工具,但是依據進數十年的經驗,可以確定的一點是:稍有不慎,這個工具就會反噬它的使用者。
終于,來解決上述難題的智能指針出現了,智能指針表現起來很像原生指針,它相當于是原生指針的一層再包裝(wrapper),但是規避了許多使用原生指針帶來的陷阱。你應該盡量使用智能指針,它幾乎能做到原生指針能做到的所有功能,卻很少給你犯錯的機會。
在C++11標準中規定了四個智能指針:std::auto_ptr, std::unique_ptr, std::shared_ptr, 以及std::weak_ptr.它們都用來設計輔助管理動態分配對象的生命周期,即,確保這些對象在正確的時間(包括發生異常時)用正確的方式進行回收,以確保不會產生內存泄露.
C++98嘗試用std::auto_ptr來標準化后來成為C++11中的std::unique_ptr的行為,為了達到目標,move語法是不可少的,但是,C++98當時還沒有move語法,所以做了個妥協方案:利用拷貝操作來模擬move.這導致了一些很讓人吃驚的代碼(如拷貝一個std::auto_ptr會將它設置為null!)和一些讓使用者覺得沮喪的使用限制(不能在容器中使用std::auto_ptr)
std::unique_ptr做到了std::auto_ptr所能做到的所有事情,而且它的實現還更高效。
智能指針的API有著顯著的區別,他們之間唯一共同的一點功能就是默認的構造方法。因為這種API詳細的介紹滿大街都是啊,所以我把重點放到了這些API介紹所沒有的知識,如:值得注意的使用場景,運行性能分析等等。掌握這些信息你就不只會可以單單的使用它們,更是學會了如何有效的運用它們。
- 出版者的忠告
- 致謝
- 簡介
- 第一章 類型推導
- 條款1:理解模板類型推導
- 條款2:理解auto類型推導
- 條款3:理解decltype
- 條款4:知道如何查看類型推導
- 第二章 auto關鍵字
- 條款5:優先使用auto而非顯式類型聲明
- 條款6:當auto推導出非預期類型時應當使用顯式的類型初始化
- 第三章 使用現代C++
- 條款7:創建對象時區分()和{}
- 條款8:優先使用nullptr而不是0或者NULL
- 條款9:優先使用聲明別名而不是typedef
- 條款10:優先使用作用域限制的enmu而不是無作用域的enum
- 條款11:優先使用delete關鍵字刪除函數而不是private卻又不實現的函數
- 條款12:使用override關鍵字聲明覆蓋的函數
- 條款13:優先使用const_iterator而不是iterator
- 條款14:使用noexcept修飾不想拋出異常的函數
- 條款15:盡可能的使用constexpr
- 條款16:保證const成員函數線程安全
- 條款17:理解特殊成員函數的生成
- 第四章 智能指針
- 條款18:使用std::unique_ptr管理獨占資源
- 條款19:使用std::shared_ptr管理共享資源
- 條款20:在std::shared_ptr類似指針可以懸掛時使用std::weak_ptr
- 條款21:優先使用std::make_unique和std::make_shared而不是直接使用new
- 條款22:當使用Pimpl的時候在實現文件中定義特殊的成員函數
- 第五章 右值引用、移動語義和完美轉發
- 條款23:理解std::move和std::forward
- 條款24:區分通用引用和右值引用
- 條款25:在右值引用上使用std::move 在通用引用上使用std::forward
- 條款26:避免在通用引用上重定義函數
- 條款27:熟悉通用引用上重定義函數的其他選擇
- 條款28:理解引用折疊
- 條款29:假定移動操作不存在,不廉價,不使用
- 條款30:熟悉完美轉發和失敗的情況
- 第六章 Lambda表達式
- 條款31:避免默認的參數捕捉
- 條款32:使用init捕捉來移動對象到閉包
- 條款33:在auto&&參數上使用decltype當std::forward auto&&參數
- 條款34:優先使用lambda而不是std::bind
- 第七章 并發API
- 條款35:優先使用task-based而不是thread-based
- 條款36:當異步是必要的時聲明std::launch::async
- 條款37:使得std::thread在所有的路徑下無法join
- 條款38:注意線程句柄析構的行為
- 條款39:考慮在一次性事件通信上void的特性
- 條款40:在并發時使用std::atomic 在特殊內存上使用volatile
- 第八章 改進
- 條款41:考慮對拷貝參數按值傳遞移動廉價,那就盡量拷貝
- 條款42:考慮使用emplace代替insert