RAII是Resource Acquisition Is Initialization的簡稱,是C++語言的一種管理資源、避免洩漏的慣用法。
RAII又叫做資源分配即初始化,即:定義一個類來封裝資源的分配和釋放,在構造函數完成資源的分配和初始化,在析構函數完成資源的清理,可以保證資源的正確初始化和釋放。
第一步和第三步缺一不可,因為資源必須要申請才能使用的,使用完成以後,必須要釋放,如果不釋放的話,就會造成資源洩漏。
所謂智能指針就是智能/自動化的管理指針所指向的動態資源的釋放。它是一個類,有類似指針的功能。
每次創建類的新對象時,初始化指針就將引用計數置為1;當對象作為另一對象的副本而創建時,拷貝構造函數拷貝指針並增加與之相應的引用計數;對一個對象進行賦值時,賦值操作符減少左操作數所指對象的引用計數(如果引用計數為減至0,則刪除對象),並增加右操作數所指對象的引用計數;調用析構函數時,析構函數減少引用計數(如果引用計數減至0,則刪除基礎對象)。
包括:std::auto_ptr、boost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_array、boost::weak_ptr、boost::intrusive_ptr
Boost庫的智能指針(ps:新的C++11標准中已經引入了unique_ptr/shared_ptr/weak_ptr):auto_ptr 獨占所有權,轉移所有權
第一種實現:最開始auto_ptr的成員變量主要有T* _ptr 和 bool _owner,主要實現原理是在構造對象時賦予其管理空間的所有權,在拷貝或賦值中轉移空間的所有權,在析構函數中當_owner為true(擁有所有權)時來釋放所有權。
template<typename T> class AutoPtr { public: //構造函數 explicit AutoPtr(T* ptr = NULL) :_ptr(ptr) , _owner(true) {} //拷貝構造 AutoPtr(AutoPtr<T>& ap) //參數不能寫成const的,這裡要修改ap對象的成員
:_ptr(ap._ptr) , _owner(true) { ap._owner = false; //轉讓權限 } //賦值運算符重載 AutoPtr& operator=(AutoPtr<T>& ap) { if (this! = &ap) { delete this->_ptr; this->_ptr = ap._ptr; // 轉讓權限 this->_owner = true; ap._owner = false; } return *this; } //析構函數 ~AutoPtr() { // 只刪除擁有權限的指針 if (_owner) { this->_owner = false; delete this->_ptr; } } T& operator*() { return *(this->_ptr); } T* operator->() { return this->_ptr; } T* AutoPtr<T>::GetStr() { return (this->_ptr); } protected: T* _ptr; bool _owner; //權限擁有者 };
出現的主要問題:如果拷貝出來的對象比原來的對象先出作用域或先調用析構函數,則原來的對象的_owner雖然為false,但卻在訪問一塊已經釋放的空間,原因在於拷貝對象的釋放會導致原對象的_ptr指向的內容跟著被釋放!
問題體現如下:
ap1將析構的權限給予了ap2,由於ap1._ptr和ap2._ptr指向同一份空間,ap2在出了if作用域之後自動被釋放,進而導致ap1._ptr也被釋放。
但是在if作用域之外,又對ap1._ptr進行訪問,導致程序崩潰,這時候 ap1._ptr已經是一個野指針了,這就造成指針的懸掛的問題!
void Test() { AutoPtr<int> ap1(new int(1)); if (true) { AutoPtr<int> ap2(ap1); } //這裡的ap1._ptr已經是一個野指針了,這就造成指針的懸掛的問題 *(ap1.GetStr() )= 10; }
auto_ptr的第二種實現方法:還是管理空間的所有權轉移,但這種實現方法中沒有_owner權限擁有者。構造和析構和上述實現方法類似,但拷貝和賦值後直接將_ptr賦為空,禁止其再次訪問原來的內存空間,比較簡單粗暴。
template<typename T> class AutoPtr { public: //構造函數 explicit AutoPtr(T* ptr = NULL) //不能寫成const T* ptr,否則出現const類型賦值給非const類型的問題 :_ptr(ptr) {} //拷貝構造 AutoPtr(AutoPtr<T>& ap) :_ptr(ap._ptr) { ap._ptr=NULL; } //賦值運算符重載 AutoPtr& operator=(AutoPtr<T>& ap) { if (this! = &ap) { delete this->_ptr; this->_ptr = ap._ptr; ap._ptr = NULL; } return *this; } //析構函數 ~AutoPtr() { // 只刪除擁有權限的指針 if (_ptr) { delete _ptr; } } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } T* GetStr() { return _ptr; } protected: T* _ptr; };
這種實現方式很好的解決了舊版本野指針問題,但是由於它實現了完全的權限轉移,所以導致在拷貝構造和賦值之後只有一個指針可以使用,而其他指針都置為NULL,使用很不方便,而且還很容易對NULL指針進行解引用,導致程序崩潰,其危害也是比較大的。
scoped_ptr 獨占所有權,防拷貝
scoped_ptr的實現原理是防止對象間的拷貝與賦值。具體實現是將拷貝構造函數和賦值運算符重載函數設置為保護或私有,並且只聲明不實現,並將標志設置為保護或私有,防止他人在類外拷貝,簡單粗暴,但是也提高了代碼的安全性。
template<typename T> class ScopedPtr { public: ScopedPtr(T* ptr = NULL) :_ptr(ptr) {} T& operator*() { return *_ptr; } T* operator->() { return _ptr; } T* GetStr() { return _ptr; } //析構函數 ~ScopedPtr() { if (_ptr!=NULL) { delete _ptr; } } protected: //防拷貝 ScopedPtr(ScopedPtr<T>& ap); ScopedPtr& operator=(ScopedPtr<T>& ap); T* _ptr; };
scoped_ptr的實現和auto_ptr非常類似,不同的是 scoped_ptr有著更嚴格的使用限制——不能拷貝,這就意味著scoped_ptr 是不能轉換其所有權的。
在一般的情況下,如果不需要對於指針的內容進行拷貝,賦值操作,而只是為了防止內存洩漏的發生,scoped_ptr完全可以勝任。
shared_ptr 共享所有權,引用計數
shared_ptr的實現原理是通過引用計數來實現,拷貝或賦值時將引用計數加1,析構時只有當引用計數減到0才釋放空間,否則只需將引用計數減1即可.
(ps:shared_ptr在新的C++11標准中叫做unique_ptr)
template<typename T> class SharedPtr { public: SharedPtr(T* ptr=NULL) :_ptr(ptr) , _refCount(new long(1)) {} ~SharedPtr() { _Release(); } SharedPtr(const SharedPtr<T>& sp) :_ptr(sp._ptr) , _refCount(sp._refCount) { ++(*_refCount); } //傳統寫法 /*SharedPtr<T>& operator=(const SharedPtr<T>& sp) { if (this != &sp) { this->_Release(); _refCount = sp._refCount; _ptr = sp._ptr; ++(*_refCount); } return *this; }*/ //現代寫法 SharedPtr<T>& operator=(SharedPtr<T> sp) { swap(_ptr, sp._ptr); swap(_refCount, sp._refCount); return *this; } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } T* GetPtr() { return _ptr; } long GetCount() { return *_refCount; } protected: void _Release() { if (--(*_refCount) == 0) { //delete _ptr; _del(_ptr); delete _refCount; } } protected: T* _ptr; long* _refCount; //引用計數 };
為什麼引用計數要設置為指針類型,而不設置成整型或靜態變量?
請看這篇文章:http://www.cnblogs.com/Lynn-Zhang/p/5400714.html
有時候看似完美的東西,往往都存在著隱含的缺陷,上面的實現仍舊存在問題!
問題如下:
第一個比較容易理解,我們可以在改變引用計數的時候加上一把互斥鎖,防止多線程帶來的隱患。
第二個,循環引用問題。我們先來看一段利用標准庫的shared_ptr 實現的代碼:
#include<iostream> using namespace std; #include<memory> struct Node { shared_ptr<Node> _prev; shared_ptr<Node> _next; ~Node() { cout << "delete :" <<this<< endl; } }; void TestSharedPtr() { shared_ptr<Node> cur(new(Node)); shared_ptr<Node> next(new(Node)); }
cur和next出了作用域,都調用析構函數被釋放了。看似正確,但是如果我們加兩句代碼,問題就出現了:
void TestSharedPtr() { shared_ptr<Node> cur(new(Node)); shared_ptr<Node> next(new(Node)); cur->_next = next; // 1 next->_prev = cur; // 2 }
我們發現兩個節點都沒有被釋放!我們來分析一下原因:加了這兩句代碼後,這兩個節點的引用計數都增加了1。出了作用域進行析構時,兩個對象均不能釋放,因為prev的要釋放的前提是next釋放,而next的釋放又依賴於prev的釋放。最後就形成了循環引用,誰都釋放不了。
解決方案:
template<class T> struct Node { public: ~Node() { cout << "delete:" << this << endl; } public: weak_ptr<Node> _prev; weak_ptr<Node> _next; }; void TestWeakPtr() { shared_ptr<Node> cur(new Node()); shared_ptr<Node> next(new Node()); cout << "連接前:" << endl; cout << "cur:" << cur.use_count() << endl; cout << "next:" << next.use_count() << endl; cur->_next = next; next->_prev = cur; cout << "連接後:" << endl; cout << "cur:" << cur.use_count() << endl; cout << "next:" << next.use_count() << endl; }
因為weak_ptr(弱引用智能指針)會對引用計數會做特殊處理(上述情況不加1)。
第三個,定制刪除器。在shared_ptr中只能處理釋放new開辟的空間,而對於malloc,以及fopen打開的文件指針不能處理,所以提出了定制刪除器,方便刪除其他類型的指針,而其實現則是通過仿函數(通過重載operator())來實現。
template <typename T, class Deleter = Del<T>> class SharedPtr { public: SharedPtr(T* ptr); SharedPtr(T* ptr, Deleter del); SharedPtr(const SharedPtr<T, Deleter>& ap); ~SharedPtr(); //SharedPtr<T, Deleter>& operator=(const SharedPtr<T, Deleter>& ptr);//傳統寫法 //現代寫法 SharedPtr<T, Deleter>& operator=(SharedPtr<T, Deleter> ap); T& operator*()const; T* operator->()const; long GetCount()const; T* GetPtr()const; protected: void _Realease(); protected: T* _ptr; long* _pCount; Deleter _del; }; template <typename T, class Deleter = Del<T>> SharedPtr<T, Deleter>::SharedPtr(T* ptr) :_ptr(ptr), _pCount(new long(1)) {} template <typename T, class Deleter = Del<T>> SharedPtr<T, Deleter>::SharedPtr(T* ptr, Deleter del) : _ptr(ptr), _pCount(new long(1)), _del(del) {} template <typename T, class Deleter = Del<T>> SharedPtr<T, Deleter>::SharedPtr(const SharedPtr<T, Deleter>& ap) : _ptr(ap._ptr), _pCount(ap._pCount), _del(ap._del) { ++(*this->_pCount); } template <typename T, class Deleter = Del<T>> SharedPtr<T, Deleter>::~SharedPtr() { this->_Realease(); } //template <typename T, class Deleter = Del<T>>//傳統寫法 //SharedPtr<T, Deleter>& SharedPtr<T, Deleter>::operator=(SharedPtr<T, Deleter> ap) //{ // if (this->_ptr != ap._ptr) // { // this->_Realease(); // this->_ptr = ap._ptr; // this->_pCount = ap._pCount; // ++(*this->_pCount); // } // return *this; //} template <typename T, class Deleter = Del<T>>//現代寫法 SharedPtr<T, Deleter>& SharedPtr<T, Deleter>::operator=(SharedPtr<T, Deleter> ap) { swap(this->_ptr, ap._ptr); swap(this->_pCount, ap._pCount); swap(this->_del, ap._del); return *this; } template <typename T, class Deleter = Del<T>> T& SharedPtr<T, Deleter>::operator*()const { return *(this->_ptr); } template <typename T, class Deleter = Del<T>> T* SharedPtr<T, Deleter>::operator->()const { return this->_ptr; } template <typename T, class Deleter = Del<T>> long SharedPtr<T, Deleter>::GetCount()const { return *(this->_pCount); } template <typename T, class Deleter = Del<T>> T* SharedPtr<T, Deleter>::GetPtr()const { return this->_ptr; } template <typename T, class Deleter = Del<T>> void SharedPtr<T, Deleter>::_Realease() { if (--(*this->_pCount) == 0) { _del(_ptr); delete this->_pCount; } } template<typename T> struct Free { void operator()(T* ptr) { cout << "Free:"<<ptr<< endl; free(ptr); } }; template<typename T> struct Fclose { void operator()(T* ptr) { cout << "Fclose:" << ptr << endl; fclose(ptr); } }; //測試代碼 void TestDelete() { int *p1 = (int*)malloc(sizeof(int)); SharedPtr<int, Free<int>> sp1(p1); FILE* p2 = fopen("test.txt", "r"); SharedPtr<FILE, Fclose<FILE>> sp2(p2); }
總結:
auto_ptr 管理權的轉移 ->不建議使用
scopd_ptr 防拷貝 ->簡單粗暴
shared_ptr 引用計數 ->增減引用計數,直到對象的引用計數為0時再釋放
scoped_array 和 shared_array 管理對象數組, 由於scopd_ptr 和 scoped_array、shared_ptr 和 shared_array 的實現都比較類似,這裡不再贅述。需要注意的是這兩種機制都重載了operator[] .
week_ptr 弱指針 ->輔助shared_ptr解決循環引用的問題。