7. 來自 Google 的奇技
我們使用了很多自己實作的技巧和工具,讓 C++ 的程式碼更加穩定可靠,我們使用 C++ 的方式可能和你在其它地方見到的有所不同。
7.1. 所有權與智慧型指標 (Ownership and Smart Pointers)
小訣竅
動態配置的物件最好擁有單一且固定的管理者 (owner)。欲傳遞所有權,最好使用智慧型指標傳。
定義:
「所有權」是一種登記、管理動態配置的記憶體(和其他資源)的技術。動態配置的物件的管理者是一個物件或函式,負責在物件不再需要的時候將物件刪除。所有權有時可以共享,此時通常最後一個管理者要負責刪除該物件。當所有權沒有共享的時候,甚至可以在不同的程式碼之間傳遞。
「智慧型指標」指的是:藉由多載
*
和->
等運算子,讓行為看起來和指標相似的類別。有些智慧型指標型別可以自動管理所有權,以確保該做的事都會完成。std::unique_ptr 是 C++11 新增的智慧型指標,用來表示動態配置的物件有獨一無二的所有權;當std::unique_ptr
物件離開作用域時,所指物件就會被刪除。std::unique_ptr
無法複製,但可以被 移動,用來表示所有權的轉移。std::shared_ptr 是用來表示動態配置的物件所有權可以被分享的智慧型指標。std::shared_ptr
可以被複製;所有複製出來的std::shared_ptr
物件共享所指物件的所有權。當最後一個std::shared_ptr
物件被銷毀時,所指物件才會被刪除。
優點:
如果沒有適當的所有權安排,幾乎不可能妥善管理動態配置的記憶體。
就算是物件可以被複製,傳遞物件的所有權所需的運算資源比複製來得小。
傳遞所有權也比「借用」指標或 reference 來得簡單,畢竟它省去了協調兩邊管理物件生命週期的工作。
智慧型指標可以明確表明所有權的邏輯,自我說明,且不會有模稜兩可的狀態,大大增進了可讀性。
智慧型指標為我們省去了手動管理所有權的工作,簡化了程式碼,也避開了大量可能發生的錯誤。
對於 const 物件來說,共享管理權可以既簡單又有效率地取代物件的深度複製 (deep copy)。
缺點:
所有權必須透過指標(不管是智慧型還是原始的)傳遞。指標的語意比純值複雜多了,尤其是在 API 中:除了所有權外,你還得留意別名 (aliasing)、生命週期、可變性 (mutability),以及其他大大小小的問題。
純值語義對執行效率的影響常被高估,因此改用所有權轉移所能得到的效能提昇,可能不足彌補在可讀性以及複雜度方面的損失。
使用轉移所有權的 API,會讓客戶端不得不使用特定的記憶體管理模型。
使用智慧型指標時,資源會在哪裡被釋放變得比較不明顯。
std::unique_ptr
利用 C++11 的 move 語意來傳遞所有權。Move 語意相對較新,有些程式員可能不太熟悉。對於所有權設計已經夠完善的系統來說,共享所有權機制雖然看來誘人,但硬要導入可能會搞亂原本的系統設計。
所有權共享機制的登記工作在執行時進行,對執行效率可能有不小的衝擊。
某些情況(像是循環參考 cyclic references)下,所有權被共享的物件可能永遠不會被刪除,。
智慧指標並不能夠完美取代原始指標。
結論:
如果需要動態配置的話,儘可能把所有權保持在配置記憶體的那段程式碼中。如果其他地方也需要存取該物件的話,考慮複製一份物件,或是傳遞物件的指標或 reference,不要傳遞所有權。最好使用
std::unique_ptr
明確表達所有權的轉移。舉例來說:std::unique_ptr<Foo> FooFactory(); void FooConsumer(std::unique_ptr<Foo> ptr);除非有很好的理由,否則不要使用所有權共享。其中一個可能的理由是為了避免很花時間的複製行為,但你只應該在省下的時間非常顯著、而且所指的物件是不會改變的(換句話說,就是
std::shared_ptr<const Foo>
),才考慮這麼做。如果你真的要共享所有權,建議使用std::shared_ptr
。禁止使用
std::auto_ptr
。請改用std::unique_ptr
。
7.2. cpplint
小訣竅
使用 cpplint.py
檢查風格錯誤。
cpplint.py
是一個用來分析原始碼並檢查出多種風格錯誤的工具。它不並完美,甚至還會漏報或誤報,但它仍然是非常有用的工具。在行尾加 // NOLINT
或在上一行加 // NOLINTNEXTLINE
可以忽略誤報。
某些專案會有如何使用他們的專案工具運行 cpplint.py
的說明。如果你參與的專案沒有提供,你可以單獨下載 cpplint.py。