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