大型項目開發:謹慎使用智能指針

智能指針使用上的問題

智能指針的使用太普遍了,它讓程序員擺脫了內存管理的惡夢,但實際上智能指針本身也可能引入另一個惡夢。主要包括兩個問題點:

  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.
發佈了220 篇原創文章 · 獲贊 29 · 訪問量 174萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章