大型專案開發:謹慎使用智慧指標

Horky發表於2015-07-13

智慧指標使用上的問題

智慧指標的使用太普遍了,它讓程式設計師擺脫了記憶體管理的惡夢,但實際上智慧指標本身也可能引入另一個惡夢。主要包括兩個問題點:

  1. 效能問題。因為需要引入一些變數(bookkeeping),甚至在多執行緒下的一些互斥操作,它所帶來的效能開銷往往比想像的要高。比如以智慧指標作為函式引數以及返回值時。
  2. 物件釋放的時機不明確。比如std::auto_ptr,總讓人感覺不明不白。而有時一些迴圈引用,又會導致記憶體洩露。

所以即便有了智慧指標,程式設計師還是要認真考慮使用它們的時機。

智慧指標的本質是所有權管理

所有權(Ownership)是一個用來管理動態分配記憶體的記錄(bookkeeping)技術。動態分配記憶體的所有者需要負責在所分配記憶體不再需要時釋放它。共享所有權時,最後一個owner負責清理。即使不是共享,也可以在程式碼間傳遞Ownership。

運用智慧指標的核心是保持所有權(Ownership)的明確、清晰。一般情況下是保證所有權的單一,無論是使用WeakPtr, Scoped Pointer都可以保持所有權的單一,這時所有權可以在不同的物件/程式碼段轉移。另一種情況就是需要共享所有權,比較常用的引用指數智慧指標,就是幫助完成共享所有權的。

概括起來三個要點:

1.區域性化
推薦保持單一、固定的所有權。當所有權需要在不同程式碼段間傳遞時,就要使用智慧指標。
2.明確的清理時機
使用引用計數,和std::auto_ptr都會引發對釋放時機的疑問。有可能引入一些隱晦的Bug。當需要共享所有權時,一定要先思考這個設計的必要性,以及記憶體釋放的時機是否明確、清晰。
3.最好的方式不要使用指標。可以使用引用來代替指標。見後面WebKit的故事。

大型專案中的應用

Google Coding Style的約定

如果需要動態分配記憶體,儘量由分配的程式碼來持有所有權。
如果另一段程式碼需要訪問物件,先考慮傳遞物件拷貝,指標或者引用,而不是傳遞所有權。如果確有需要時,建議使用std::unique_ptr來顯示的傳遞所有權(使用std::move())。
除非有好的理由,否則不要共享所有權。比如為了避免複製。這時需要確定有明顯的效能收益,而且所持有的物件最好是隻讀的, 同時建議使用std::shared_ptr:

std::shared_ptr<const Foo>;

新程式碼裡不要再使用scoped_ptr,更不要使用std::auto_ptr, 而是使用std::unique_ptr來代替。

WebKit的故事

WebKit文件記錄了他們使用引用計數指標的故事,詳情見:RefPtr Basics。大意為:
早在2005年時為了解決記憶體洩露問題,開始使用基於引用計數的智慧指標,但是它有效能問題,特別是當作函式引數和返回值傳遞時。後來使用C++11提供move語義(即轉移所有權的方式)來解決了這個問題。(另外shared_ptr const & 的形式也可以避免不必要的引用計數操作。)

而到了2013年,WebKit的開發者發現氾濫的判空和有效性檢查,於是開始傾向於儘可能地使用引用(注意:不是引用計數指標), 而不是指標。

小結

解決記憶體問題的最佳的途徑仍然是由程式設計師管理好物件的生命週期。使用智慧指標也是有成本的,同時也會引入一些新問題,所以需要遵循一些約定來使用。Google Chromium/Webkit都有相應用的定義:

另外明確約定一個函式是否會返回空指標,特別是對於API來說,也會幫助使用者避免一些不必要的判空處理和一些隱晦的Bug。

進一步學習:

  1. unique_ptr使用簡介
  2. Reference counting smart pointers are for retards.

相關文章