智能指針原理及實現(1)shared_ptr

0、異常安全

C++沒有內存回收機制,每次程序員new出來的對象須要手動delete,流程複雜時可能會漏掉delete,致使內存泄漏。因而C++引入智能指針,可用於動態資源管理,資源即對象的管理策略。git

使用 raw pointer 管理動態內存時,常常會遇到這樣的問題:程序員

  • 忘記delete內存,形成內存泄露。
  • 出現異常時,不會執行delete,形成內存泄露。

下面的代碼解釋了,當一個操做發生異常時,會致使delete不會被執行:github

1 void func() 2 { 3     auto ptr = new Widget; 4     // 執行一個會拋出異常的操做
5  func_throw_exception(); 6     
7     delete ptr; 8 }

在C++98中,爲了寫出異常安全的代碼,代碼常常寫的很笨拙,以下:安全

 1 void func()  2 {  3     auto ptr = new Widget;  4     try {  5  func_throw_exception();  6  }  7     catch(...) {  8         delete ptr;  9         throw; 10  } 11     delete ptr; 12 }

使用智能指針能輕易寫出異常安全的代碼,由於當對象退出做用域時,智能指針將自動調用對象的析構函數,避免內存泄露。函數

1、智能指針shared_ptr

智能指針主要有三種:shared_ptrunique_ptrweak_ptr測試

 shared_ptrthis

shared_ptr是最經常使用的智能指針(項目中我只用過shared_ptr)。shared_ptr採用了引用計數器,多個shared_ptr中的T *ptr指向同一個內存區域(同一個對象),並共同維護同一個引用計數器。shared_ptr定義以下,記錄同一個實例被引用的次數,當引用次數大於0時可用,等於0時釋放內存。spa

注意避免循環引用,shared_ptr的一個最大的陷阱是循環引用,循環,循環引用會致使堆內存沒法正確釋放,致使內存泄漏。循環引用在weak_ptr中介紹。線程

1 temple<typename T>
2 class SharedPtr { 3 public: 4  ... 5 private: 6     T *_ptr; 7     int *_refCount;     //should be int*, rather than int
8 };

shared_ptr對象每次離開做用域時會自動調用析構函數,而析構函數並不像其餘類的析構函數同樣,而是在釋放內存是先判斷引用計數器是否爲0。等於0才作delete操做,不然只對引用計數器左減一操做。指針

1 ~SharedPtr() 2 { 3     if (_ptr && --*_refCount == 0) { 4         delete _ptr; 5         delete _refCount; 6  } 7 }

 

接下來看一下構造函數,默認構造函數的引用計數器爲0,ptr指向NULL:

1 SharedPtr() : _ptr((T *)0), _refCount(0) 2 { 3 }

用普通指針初始化智能指針時,引用計數器初始化爲1:

1 SharedPtr(T *obj) : _ptr(obj), _refCount(new int(1)) 2 { 3 } //這裏沒法防止循環引用,若咱們用同一個普通指針去初始化兩個shared_ptr,此時兩個ptr均指向同一片內存區域,可是引用計數器均爲1,使用時須要注意。

拷貝構造函數須要注意,用一個shared_ptr對象去初始化另外一個shared_ptr對象時,引用計數器加一,並指向同一片內存區域:

1 SharedPtr(SharedPtr &other) : _ptr(other._ptr), _refCount(&(++*other._refCount)) 2 { 3 }

 

賦值運算符的重載

當用一個shared_ptr<T> other去給另外一個 shared_ptr<T> sp賦值時,發生了兩件事情:

1、sp指針指向發生變化,再也不指向以前的內存區域,因此賦值前原來的_refCount要自減

2、sp指針指向other.ptr,因此other的引用計數器_refCount要作++操做。

 1 SharedPtr &operator=(SharedPtr &other)  2 {  3     if(this==&other)  4         return *this;  5         
 6     ++*other._refCount;  7     if (--*_refCount == 0) {  8         delete _ptr;  9         delete _refCount; 10  } 11         
12     _ptr = other._ptr; 13     _refCount = other._refCount; 14     return *this; 15 }

 

定義解引用運算符,直接返回底層指針的引用:

1 T &operator*() 2 { 3     if (_refCount == 0) 4         return (T*)0; 5         
6     return *_ptr; 7 }

 

定義指針運算符->

1 T *operator->() 2 { 3     if(_refCount == 0) 4         return 0; 5         
6     return _ptr; 7 }

 

2、測試

1 int main(int argc, const char * argv[]) 2 { 3     SharedPtr<string> pstr(new string("abc")); 4     SharedPtr<string> pstr2(pstr); 5     SharedPtr<string> pstr3(new string("hao")); 6     pstr3 = pstr2; 7     
8     return 0; 9 }

爲了讓測試結果更明顯,我在方法中加入了一些輸出,測試結果以下:

 源碼連接:https://github.com/guhowo/test/tree/master/cplus/SharedPtr

 

思考

一、本文這種寫法不是線程安全的,是吧?

二、boost中的shared_ptr線程安全嗎?

相關文章
相關標籤/搜索