C++記憶體洩漏
對於記憶體洩露,這是一個很重要的問題,我們分析了幾個例子,來更全面的定義記憶體洩露。
例如如果我們分配了記憶體(new操作符),釋放必須由某物件負責:它必須使用正確的delete操作符 刪除這塊記憶體,並且該任務只執行一次。 這裡把刪除記憶體的責任稱為物件的所有權,
所以記憶體洩露是由於被分配的記憶體的所有權丟失了。怎麼解決呢?方案就是:
當我們分配新記憶體時,必須立即把指向這塊記憶體的指標賦值給某個智慧指標,這樣媽媽再也不用擔心刪除這塊記憶體的問題了,該任務之後完全由智慧指標負責,那關於智慧指標我們需要注意些什麼呢?
- 是否允許對智慧指標進行復制
- 如果是,在智慧指標的多份拷貝中,到底哪一個負責刪除它們共同指向的物件?
- 智慧指標是否表示指向一個物件陣列或物件的指標
- 智慧指標是否對應於一個常量指標和非常量指標
取決於這些問題的答案,我們有多種不同的智慧指標,C++社群中有stl的auto_ptr和boost庫的shared_ptr,後者是更值得使用的。
這裡有兩類智慧指標,能夠滿足前面討論的所有需要,有效的防止記憶體洩露。不同之處在於:引用計數指標可以被複制,而作用域指標不能被複制,但作用域指標效率更高,下面具體來討論它:
1.引用計數指標
引用計數指標可以被複制,因此一個智慧指標的幾份拷貝可以指向同一個物件,這就產生了由哪份拷貝負責刪除它們共同指向的物件這個問題。
答: 這組智慧指標中最後消亡的那個將刪除它所指向的物件(類似於最後一個屋子的人負責關燈的概念)。
其中這些指標共享一個計數器,記錄有多少個智慧指標引用同一個物件。這表明當有人建立一個指向目標物件的智慧指標的一份拷貝是,計數加1,反之任何智慧指標刪除時,計數減1,同時我們也意識到該指標不是執行緒安全的並且建立計數指標的實參開銷比較大。
//scpp_vector.h #ifndef _SCPP_VECTOR_ #define _SCPP_VECTOR_ #include <vector> #include "scpp_assert.h" namespace scpp { //wrapper around std::vector,在[]提供了臨時的安全檢查:過載[] <<運算子template<typename T> class vector : public std::vector<T> { public: typedef unsigned size_type; //常用的建構函式 commonly use cons explicit vector(size_type n=0) : std::vector<T>(n) {} vector(size_type n,const T& value) : std::vector<T>(n,value) {} template <class InputIterator> vector(InputIterator first,InputIterator last) : std::vector<T>(first,last) {} //Note : we don't provide a copy-cons and assignment operator ? //使用scpp::vector提供更安全的下標訪問實現,它可以捕捉越界訪問錯誤 T& operator[] (size_type index) { SCPP_ASSERT( index < std::vector<T>::size() , "Index " << index << " must be less than " << std::vector<T>::size()); return std::vector<T>::operator[](index); }//? difference const T& operator[] (size_type index) const { SCPP_ASSERT( index < std::vector<T>::size() , "Index " << index << " must be less than " << std::vector<T>::size()); return std::vector<T>::operator[](index); } //允許此函式訪問這個類的私有資料 //friend std::ostream& operator<< (std::ostream& os,const ) ? }; } //namespace scpp template<typename T> inline std::ostream& operator<< (std::ostream& os,const scpp::vector<T>& v) { for(unsigned i=0 ;i<v.size();i++) { os << v[i]; if( i+1 < v.size()) os << " "; } return os; } #endif
2.作用域指標
當我們並不打算複製智慧指標,只是想保證被分配的資源被正確的回收,可採用一個更簡單的方法-作用域指標
#ifndef _SCPP_SCOPEDPTR_H_
#define _SCPP_SCOPEDPTR_H_
#include "scpp_assert.h"
namespace scpp {
template<typename T>
class ScopedPtr {
public:
explicit ScopedPtr(T *p=NULL) :m_ptr(p) {}
ScopedPtr<T>& operator=(T *p) {
if(m_ptr !=p) {
delete m_ptr;
m_ptr=p;
}
return *this;
}
~ScopedPtr() {
delete m_ptr;
}
T* get() const {
return m_ptr;
}
T* operator->() const {
SCPP_ASSERT(m_ptr!=NULL,"Attempt to use operator -> on NULL pointer.");
return m_ptr;
}
T& operator* () const {
SCPP_ASSERT(m_ptr!=NULL,"Attempt to use operator * on NULL pointer.");
return *m_ptr;
}
//把物件的所有權釋放給原始呼叫者
T* release() {
T *p=m_ptr;
m_ptr=NULL;
return p;
}
private:
T *m_ptr;
//copy is forbidden
ScopedPtr(const ScopedPtr<T>& rhs);
ScopedPtr<T>& operator=(const ScopedPtr<T>& rhs);
};
} //namespace scpp
#endif
//test_vector.cpp
#include "scpp_vector.h"
#include <iostream>
using namespace std;
int main() {
//usage-建立一個具有指定數量的vector:scpp::vector<int> v(n); 把n個vector元素都初始化為一個值:scpp::vector<int> v(n,val)
//方法3:scpp::vector<int> v; v.reserve(n),表示開始的vector是空的,對應的size()為0,
//並且開始新增元素時,在長度達到n之前,不會出現導致速度降低的容量增長現象
scpp::vector<int> vec;
for(int i=0;i< 3;i++){
vec.push_back(4*i);
}
cout << "The vector is : "<< vec << endl;
for(int i=0;i <= vec.size();i++) {
cout << "Value of vector at index " << i << " is " << vec[i] << endl;
}
return 0;
}
該類最重要的屬性就是解構函式刪除它指向的物件,並且作用域指標不能被複制(因為都宣告為了私有,任何試圖複製這種指標的程式碼都無法通過編譯,這樣就消除了對指向同一個物件的同一個智慧指標的多份拷貝計數的需要)
3.用智慧指標實行所有權
用法:
//不需要呼叫者負責刪除這個物件,而是讓函式返回一個智慧指標
//更多的依賴編譯器而不是程式設計師
RefCountPtr<A> res(new A(inputs));
ScopedPtr<A> res; //建立一個空作用域指標
void B::create(const Input& ins,ScopedPtr<A>& res);
//建立一個NULL值的作用域指標,並用下面的方法填充它,
//這種方法也不會使該函式建立的物件所有權出現錯誤
4.解引用NULL指標
對於解引用NULL指標,一般只需要在聲明後或者分配記憶體後判斷下指標是否為NULL指標即可,樓主的辦法只是更為抽象而已,這裡就不再描述。