c++ 中的智慧指標
目錄
1. 初識智慧指標
1.1 記憶體洩漏的原因分析
1.2 記憶體洩漏的解決方案
2. 智慧指標類模板
2.1 智慧指標的意義
2.2 STL 中的智慧指標應用
2.3 QT 中的智慧指標應用
2.4 智慧指標模板類的實現
初識智慧指標
在c++語言中沒有垃圾回收機制,記憶體洩漏這個問題就不得不讓程式設計師自己來解決,稍有不慎就會給軟體帶來Bug,幸運的是我們可以使用智慧指標去解決記憶體洩漏的問題。
1、 記憶體洩漏的原因分析
(1)動態申請堆空間,用完後不歸還;
(2)C++ 語言中沒有垃圾回收的機制;
1)Java、C# 語言中都引入了垃圾回收機制,定期檢測記憶體,若發現沒有使用,則回收;
2)垃圾回收機制可以很好的避免記憶體洩漏;
3)C++ 中的動態記憶體申請和歸還完全依賴開發者,稍有不慎就會出錯;
(3)指標無法控制所指堆空間的生命週期;(根本原因)
1)通過指標可以指向動態記憶體空間,但是卻不能夠控制動態記憶體空間的生命週期;
2)也就是指標和動態記憶體空間沒有必然聯絡,即使指標變數銷燬了,動態記憶體空間還可以存在;
補充:多次釋放多個指標指向的同一塊堆空間也會使軟體出現Bug;
1 #include <iostream> 2 #include <string> 3 4 using namespace std; 5 6 class Test 7 { 8 int i; 9 public: 10 Test(int i) 11 { 12 this->i = i; 13 } 14 int value() 15 { 16 return i; 17 } 18 ~Test() 19 { 20 } 21 }; 22 23 int main() 24 { 25 for(int i=0; i<5; i++) 26 { 27 // 指標p指向所申請的堆空間,但是並沒有手動歸還這塊記憶體;當進行下一次迴圈時,指標p又指向了一塊新的堆空間,這樣前一次的堆空間就永遠無法歸還了, 28 // 同時,指標p是一個區域性變數,for迴圈結束後指標P就銷燬了,這就意味著這片空間永遠無法歸還了; 29 Test* p = new Test(i); 30 31 cout << p->value() << endl; 32 33 // delete p; // 正確做法:每次用完之後記得歸還所申請的堆空間,否則就會造成記憶體洩漏 34 } 35 36 return 0; 37 }
2、記憶體洩漏的解決方案
需求分析 -> 解決方案:(結合案列更容易理解)
(1)需要一個特殊的指標,即智慧指標物件(普通類物件,通過過載指標操作符就可以使物件指向堆空間),通過類的建構函式完成;
(2)指標生命週期結束時主動釋放堆空間,可以通過類的解構函式完成;
(3)一片堆空間最多隻能由一個指標標識,為的是避免多次釋放記憶體,通過拷貝建構函式和賦值操作符完成;
(4)杜絕指標運算和指標比較;
1) 杜絕指標運算可以避免指標越界和野指標;
2)上面的第三個需求滿足了,指標比較就沒有意義了;
3)不過載類的運算子(算術運算子、關係運算符、++、--),當進行指標(類物件)運算與比較時,程式會編譯失敗。
(5)過載指標特徵操作符(-> 和 *);
1)通過過載指標操作符使得類物件具備了指標的行為;
2)建立一個類物件,讓這個物件通過操作符過載模擬真正的指標行為;
注:只能通過類的成員函式過載指標操作符,且該過載函式不能使用引數;
通過類的成員函式過載指標特徵操作符,從而使得類物件可以模擬指標的行為,這個類物件稱為智慧指標。
使用智慧指標的注意事項:只能指向堆空間的物件或變數,不允許指向棧物件。
智慧指標的表現形象:使用類物件來取代指標。
1 #include <iostream> 2 3 using namespace std; 4 5 class Test 6 { 7 int i; 8 public: 9 Test(int i) 10 { 11 cout << "Test(int i)::" << i << endl; 12 this->i = i; 13 } 14 int value() 15 { 16 return i; 17 } 18 ~Test() 19 { 20 cout << "~Test()::" << i << endl; 21 } 22 }; 23 24 class Pointer 25 { 26 private: 27 Test *mp; 28 public: 29 Pointer(Test *p = NULL) 30 { 31 mp = p; 32 } 33 Pointer(const Pointer& obj) 34 { 35 mp = obj.mp; 36 const_cast<Pointer&>(obj).mp = NULL; 37 } 38 Pointer& operator=(const Pointer& obj) 39 { 40 if(this != &obj) 41 { 42 if(mp != NULL) 43 { 44 delete mp; 45 } 46 mp = obj.mp; 47 const_cast<Pointer&>(obj).mp = NULL; 48 } 49 50 return *this; 51 } 52 Test* operator->() 53 { 54 return mp; 55 } 56 Test& operator*() 57 { 58 return *mp; 59 } 60 bool isNull() 61 { 62 return (mp == NULL); 63 } 64 ~Pointer() 65 { 66 delete mp; 67 } 68 69 }; 70 71 int main(int argc, char const *argv[]) 72 { 73 cout << "-------1-------------" << endl; 74 Pointer pt1 = new Test(10); // Test(int i)::10 75 cout << pt1->value() << endl; // 10 76 cout << (*pt1).value() << endl; // 10 77 78 cout << "-------2-------------" << endl; 79 Pointer pt2 = new Test(5); // Test(int i)::5 80 cout << pt2->value() << endl; // 5 81 82 cout << "-------3-------------" << endl; 83 Pointer pt3 = pt2; // 將指標pt2的使用權交給指標pt3 84 cout << pt2.isNull() << endl; // 1 85 cout << pt3->value() << endl; // 5 86 87 cout << "-------4-------------" << endl; 88 pt3 = pt1; // 將指標pt1的使用權交給指標pt3 // ~Test()::5 89 cout << pt1.isNull() << endl; // 1 90 91 cout << "-------5-------------" << endl; 92 Pointer pt4; 93 pt4 = pt3; // 將指標pt3的使用權交給指標pt4 // ~Test()::10 94 cout << pt3.isNull() << endl; // 1 95 96 return 0; 97 } 98 99 /** 100 * 智慧指標的需求: 101 * 指標的生命週期結束時,主動的釋放堆空間 102 * 一片堆空間最多隻能由一個指標標識 103 * 杜絕指標運算和指標比較 104 * 105 * 使用智慧指標的注意事項:只能指向堆空間的物件或變數,不允許指向棧變數 106 * 智慧指標的表現形象:使用類物件來取代指標 107 * 108 */智慧指標的實現
注:這個案列只實現了一個類的記憶體回收,關於任意類的記憶體回收,會在後續的模板技術中介紹。
智慧指標類模板
知識回顧
由於智慧指標相關的類過載了指標操作符 ,所以其物件可以像原生的指標一樣使用,本質上智慧指標物件就是類物件。但是,此時的智慧指標物件有很大的侷限性,不能靈活的指向任意的類物件。為了解決這個問題,智慧指標類模板就出現了。
1、智慧指標的意義
(1)現代 C++ 開發庫中最重要的類模板之一;(如 STL 標準庫、Qt )
(2)是C++開發中自動記憶體管理的主要手段;
(3)能夠在很大程度上避開記憶體相關的問題。
2、STL中的智慧指標應用
(1) auto_ptr
1)生命週期結束時,銷燬指向的記憶體空間;(避免只借不還的現象出現)
2)不能指向堆陣列,只能指向堆物件(變數);(若需要使用堆陣列,我們可以自己實現記憶體回收機制)
3)一片堆空間只屬於一個智慧指標物件;或者,多個智慧指標物件不能指向同一片堆空間;(避免多次釋放同一個指標;)
(2)shared_ptr
帶有引用計數機制,支援多個指標物件指向同一片記憶體;
(3)weak_ptr
配合 shared_ptr 而引入的一種智慧指標;
(4)unique_ptr
一個指標物件指向一片記憶體空間,不能拷貝構造和賦值(auto_ptr 的進化版,沒有使用權的轉移);
1 #include <iostream> 2 #include <string> 3 #include <memory> // 智慧指標類模板的標頭檔案 4 5 using namespace std; 6 7 class Test 8 { 9 string m_name; 10 public: 11 Test(const char* name) 12 { 13 cout << "construct @" << name << endl; 14 15 m_name = name; 16 } 17 18 void print() 19 { 20 cout << "member @" << m_name << endl; 21 } 22 23 ~Test() 24 { 25 cout << "destruct @" << m_name << endl; 26 } 27 }; 28 29 int main() 30 { 31 auto_ptr<Test> pt(new Test("smartPoint")); 32 33 cout << "pt = " << pt.get() << endl; // pt.get() 返回指標所指向陣列的首地址 34 35 pt->print(); 36 37 cout << endl; 38 39 auto_ptr<Test> pt1(pt); // pt 轉移了對堆空間的控制權,指向 NULL; 40 41 cout << "pt = " << pt.get() << endl; 42 cout << "pt1 = " << pt1.get() << endl; 43 44 pt1->print(); 45 46 return 0; 47 } 48 /** 49 * 執行結果: 50 * construct @smartPoint 51 * pt = 0x1329c20 52 * member @smartPoint 53 * 54 * pt = 0 55 * pt1 = 0x1329c20 56 * member @smartPoint 57 * destruct @smartPoint 58 * /auto_ptr 使用案列
3、QT 中的智慧指標應用
(1)QPointer
1)當其指向的物件被銷燬(釋放)時,它會被自動置空;
可以使用多個 QPointer 智慧指標指向同一個物件,當這個物件被銷燬的時候,所有的智慧指標物件都變為空,這可以避免多次釋放和野指標的問題。
2)析構時不會自動銷燬所指向的物件;(!!!)
也就是當 QPointer 物件生命週期完結的時候,不會自動銷燬堆空間中的物件,需要手動銷燬;
(2)QSharedPointer(和 STL中shared_ptr 相似)
1)引用計數型智慧指標(引用計數的物件是堆空間申請的物件);
2)可以被自由地拷貝和賦值;
3)當引用計數為 0 時,才刪除指向的物件;(這個智慧指標物件生命週期結束後,引用計數減一)
(3)其它的智慧指標(QweakPointer;QScopedPointer;QScopedArrayPointer;QSharedDataPointer;QExplicitlySharedDataPointer;)
為什麼QT要重新開發自己的記憶體管理機制,而不直接使用已有的STL中智慧指標?
這個和它的架構開發思想相關,因為 Qt 有自己的記憶體管理思想,但是這些思想並沒有在 STL 中實現,為了將這種記憶體管理思想貫徹到 Qt 中的方方面面,所以Qt 才開發自己的智慧指標類模板。
1 #include <QPointer> 2 #include <QSharedPointer> 3 #include <QDebug> 4 5 class Test : public QObject // Qt 開發中都要將類繼承自 QObject 6 { 7 QString m_name; 8 public: 9 Test(const char* name) 10 { 11 qDebug() << "construct @" << name; 12 13 m_name = name; 14 } 15 16 void print() 17 { 18 qDebug() << "member @" << m_name; 19 } 20 21 ~Test() 22 { 23 qDebug() << "destruct @" << m_name ; 24 } 25 }; 26 27 int main() 28 { 29 QPointer<Test> pt(new Test("smartPoint")); 30 QPointer<Test> pt1(pt); 31 QPointer<Test> pt2(pt); 32 33 pt->print(); 34 pt1->print(); 35 pt2->print(); 36 37 delete pt; // 手工刪除,這裡只用刪除一次就可,上述三個指標都指向NULL; 38 39 qDebug() << "pt = " << pt; 40 qDebug() << "pt1 = " << pt1; 41 qDebug() << "pt2 = " << pt2; 42 43 qDebug() << "QPointer 與 QSharedPointer 的區別" << endl; 44 45 QSharedPointer<Test> spt(new Test("smartPoint")); // 引用計數是相對於 Test("smartPoint") 物件而言; 46 QSharedPointer<Test> spt1(spt); 47 QSharedPointer<Test> spt2(spt); 48 49 spt->print(); 50 spt1->print(); 51 spt2->print(); 52 53 return 0; 54 } 55 56 /** 57 * 執行結果: 58 * construct @ smartPoint 59 * member @ "smartPoint" 60 * member @ "smartPoint" 61 * member @ "smartPoint" 62 * destruct @ "smartPoint" 63 * pt = QObject(0x0) 64 * pt1 = QObject(0x0) 65 * pt2 = QObject(0x0) 66 * 67 * QPointer 與 QSharedPointer 的區別 68 * 69 * construct @ smartPoint 70 * member @ "smartPoint" 71 * member @ "smartPoint" 72 * member @ "smartPoint" 73 * destruct @ "smartPoint" 74 * /QPointer 和 QSharedPointer 使用案列
4、智慧指標模板類的實現
參照 auto_ptr 的設計,同樣會在拷貝建構函式和賦值操作符中發生堆空間控制權的轉移。
1 // smartPointer.hpp 智慧指標模板類 2 #ifndef SMARTPOINTER_H 3 #define SMARTPOINTER_H 4 5 template 6 <typename T> 7 class SmartPointer 8 { 9 private: 10 T *mp; 11 public: 12 SmartPointer(T *p = 0); 13 SmartPointer(const SmartPointer<T>& obj); 14 SmartPointer<T>& operator=(const SmartPointer<T>& obj); 15 T* operator->(); 16 T& operator*(); 17 bool isNull(); 18 T* get(); 19 ~SmartPointer(); 20 21 }; 22 23 template 24 <typename T> 25 SmartPointer<T>::SmartPointer(T *p) 26 { 27 mp = p; 28 } 29 30 template 31 <typename T> 32 SmartPointer<T>::SmartPointer(const SmartPointer<T>& obj) 33 { 34 mp = obj.mp; 35 const_cast<SmartPointer&>(obj).mp = 0; 36 } 37 38 template 39 <typename T> 40 SmartPointer<T>& SmartPointer<T>::operator=(const SmartPointer<T>& obj) 41 { 42 if(this != &obj) 43 { 44 if(mp != 0) 45 { 46 delete mp; 47 } 48 mp = obj.mp; 49 const_cast<SmartPointer<T>&>(obj).mp = 0; 50 } 51 52 return *this; 53 } 54 55 template 56 <typename T> 57 T* SmartPointer<T>::operator->() 58 { 59 return mp; 60 } 61 62 template 63 <typename T> 64 T& SmartPointer<T>::operator*() 65 { 66 return *mp; 67 } 68 69 template 70 <typename T> 71 bool SmartPointer<T>::isNull() 72 { 73 return (mp == 0); 74 } 75 76 template 77 <typename T> 78 T* SmartPointer<T>::get() 79 { 80 return mp; 81 } 82 83 template 84 <typename T> 85 SmartPointer<T>::~SmartPointer() 86 { 87 delete mp; 88 } 89 90 #endif 91 92 // main.cpp 測試檔案 93 94 #include <iostream> 95 #include "test.hpp" 96 97 using namespace std; 98 99 class Test 100 { 101 string m_name; 102 public: 103 Test(const char* name) 104 { 105 cout << "construct @" << name << endl; 106 107 m_name = name; 108 } 109 110 void print() 111 { 112 cout << "member @" << m_name << endl; 113 } 114 115 ~Test() 116 { 117 cout << "destruct @" << m_name << endl; 118 } 119 }; 120 121 class Demo 122 { 123 string m_name; 124 public: 125 Demo(const char* name) 126 { 127 cout << "construct @" << name << endl; 128 129 m_name = name; 130 } 131 132 void print() 133 { 134 cout << "member @" << m_name << endl; 135 } 136 137 ~Demo() 138 { 139 cout << "destruct @" << m_name << endl; 140 } 141 }; 142 143 144 145 int main(int argc, char const *argv[]) 146 { 147 SmartPointer<Test> pt(new Test("SmartPointer Template <Test>")); 148 149 cout << "pt = " << pt.get() << endl; 150 151 pt->print(); 152 153 cout << endl; 154 155 SmartPointer<Test> pt1(pt); 156 157 cout << "pt = " << pt.get() << endl; 158 cout << "pt1 = " << pt1.get() << endl; 159 160 pt1->print(); 161 162 //--------------------------------------------------------------- 163 cout << "--------------------------------------------" << endl; 164 165 SmartPointer<Demo> spt(new Demo("SmartPointer Template <Demo>")); 166 167 cout << "spt = " << spt.get() << endl; 168 169 spt->print(); 170 171 cout << endl; 172 173 SmartPointer<Demo> spt1(spt); 174 175 cout << "spt = " << spt.get() << endl; 176 cout << "spt1 = " << spt1.get() << endl; 177 178 spt1->print(); 179 180 return 0; 181 } 182 /** 183 * 執行結果: 184 * construct @SmartPointer Template <Test> 185 * pt = 0x17bcc20 186 * member @SmartPointer Template <Test> 187 * 188 * pt = 0 189 * pt1 = 0x17bcc20 190 * member @SmartPointer Template <Test> 191 * ----------------------------------------- 192 * construct @SmartPointer Template <Demo> 193 * spt = 0x17bd090 194 * member @SmartPointer Template <Demo> 195 * 196 * spt = 0 197 * spt1 = 0x17bd090 198 * member @SmartPointer Template <Demo> 199 * destruct @SmartPointer Template <Demo> 200 * destruct @SmartPointer Template <Test> 201 */auto_ptr 類模板實現案列
本節總結:
智慧指標能夠儘可能的避開記憶體相關的問題,主要表現在:
(1)記憶體洩漏
(2)多次釋放
&n