記憶體泄漏、指針操作符重載、類模板技術、auto_ptr 指針 ...
目錄
初識智能指針
在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 @