智能指針?是一個指針嗎?這里給大家說的是,它不是一個指針,但它模擬了指針所具有的功能。那么,為什么要有智能指針的引入呢?看看下面的例子吧~
void FunTest()
{int *p = new int[10];FILE *pFile = fopen("1.txt","r");if(pFile == NULL){return;}if(p){delete[] p;p = NULL;}
}
在c++動態內存分配空間時,是由用戶自己維護的,所以當new出來空間后,必須由用戶手動釋放。再看看上面的這個代碼,有什么問題呢?顯然的,當1.txt這個文件不存在時,pFile就為空,這就使后面釋放p的語句沒有執行,導致內存泄漏。那么怎么解決這類問題呢?
這就引入了我們重要的智能指針,所謂智能指針就是智能化的管理動態開辟的資源的釋放工作。
1、模擬實現auto_ptr
template<typename T>
class AutoPtr
{
public:AutoPtr(T* p):_p(new T(1)){cout<<"AutoPtr()"<<endl;_p = p;}AutoPtr(AutoPtr& ap):_p(ap._p){cout<<"AutoPtr(AutoPtr& ap)"<<endl;ap._p = NULL;}AutoPtr<T>& operator=(AutoPtr& ap){cout<<"AutoPtr<T>& operator=(AutoPtr& ap)"<<endl;if(this != &ap){delete _p;_a = ap._p;ap._p = NULL;}}~AutoPtr(){cout<<"~AutoPtr()"<<endl;if (_p){delete _p;_p = NULL;}}
public:T& operator*(){return *_p;}T* operator->(){return _p;}
private:T *_p;
};void FunTest()
{AutoPtr<int> ap = new int;AutoPtr<int> ap1(ap);*ap1 = 10;*(ap1.operator->()) = 20;
}
int main()
{FunTest();return 0;
}
智能指針AutoPtr的特點是:只可以管理一個對象,看一下監視窗口:
這樣就會導致資源被轉移。
當然,AutoPtr還有另一種實現方式:
template<typename T>
class AutoPtr
{
public:AutoPtr(T* p):_p(new T(1)){cout<<"AutoPtr()"<<endl;_p = p;_owner = true;}AutoPtr(AutoPtr& ap):_p(ap._p){cout<<"AutoPtr(AutoPtr& ap)"<<endl;ap._owner = false;ap._p = NULL;_owner = true;}AutoPtr<T>& operator=(AutoPtr& ap){cout<<"AutoPtr<T>& operator=(AutoPtr& ap)"<<endl;if(this != &ap){delete _p;_a = ap._p;ap._owner = false;ap._p = NULL;_owner = true;}}~AutoPtr(){cout<<"~AutoPtr()"<<endl;if (_p){delete _p;_p = NULL;_owner = false;}}
public:T& operator*(){return *_p;}T* operator->(){return _p;}
private:T *_p;bool _owner;
};void FunTest()
{AutoPtr<int> ap = new int;AutoPtr<int> ap1(ap);*ap1 = 10;*(ap1.operator->()) = 20;}int main()
{FunTest();return 0;
}
這種方式就是定義一個私有成員,用來標記當前對象的狀態,若為false,表示當前對象已不再指向任何空間,若為true,說明該對象指向申請的那塊內存。
那么,這種類型的指針有什么弊端嗎?首先,只能管理單個對象,每次只有一個對象可使用申請的空間。其次,使得資源轉移,另一個對象使用時,前一個對象將賦為空。
但是切記不要使用auto_ptr。
2、模擬實現scoped_ptr
scoped_ptr與auto_ptr一樣,都是管理單個對象的。它的作用是:在一個類中防止拷貝。說起防止拷貝,大家會想到把它定義為私有的,總可以了吧。其實并不可以,因為訪問私有成員或函數的方式還有將調用它的函數聲明為類的友元就ok啦。所以,在這里,我們將學到一種防拷貝的方式:只聲明不定義,且將拷貝構造和賦值運算符重載聲明為私有即可。
實現如下:
template<typename T>
class ScopedPtr
{
public:ScopedPtr(const T* p):_p(p){}~ScopedPtr(){if(_p){delete _p;_p = NULL;}}T& operator*(){return *_p;}T* operator->(){return _p;}
private:ScopedPtr(const ScopedPtr&);ScopedPtr<T>& operator=(const ScopedPtr&);
private:T* _p;
};void FunTest()
{ScopedPtr<int> sp = new int;ScopedPtr<int> sp1(sp); //error,這樣就使用不了拷貝構造函數了ScopedPtr<int> sp1 = sp; //error,使用不了賦值運算符重載}int main()
{FunTest();return 0;
}
scoped_ptr實現的機制體現了它的獨占性,當一個對象占用一塊空間時,其他對象將無法使用。并且解決了不讓資源轉移的問題。
注:在STL源碼庫中使用的是scoped_ptr,而在boost庫中,使用的是unique_ptr。兩個其實是一樣的。
3、模擬實現shared_ptr
前面兩個智能指針都是管理單個對象,而這個shared_ptr則是可以管理多個對象。它們之間的資源是共享的。
以下代碼是使用引用計數的方式實現資源共享。
實現代碼如下:
template<typename T>
class SharedPtr
{
public:SharedPtr(T* p = NULL):_p(p),_pCount(new int(1)){cout<<"SharedPtr(T* p = NULL)"<<endl;}SharedPtr(SharedPtr& sp):_p(sp._p),_pCount(sp._pCount){cout<<"SharedPtr(SharedPtr& sp)"<<endl;}SharedPtr<T>& operator=(const SharedPtr& sp){cout<<"SharedPtr<T>& operator=(const SharedPtr& sp)"<<endl;if(_p != sp._p){if(_p == NULL){_p = sp._p;_pCount = sp._pCount;}else if(_p && (*_pCount== 1)){delete _p;_p = sp._p;_pCount = sp._p;}else{--(*_pCount);_p = sp._p;_pCount = sp._pCount;}if(sp._p)++(*_pCount);}return *this;}~SharedPtr(){cout<<"~SharedPtr()"<<endl;if(_p && --(*_pCount) == 0){delete _p;_p = NULL;}}
public:T& operator*(){return *_p;}T* operator->(){return _p;}int UseCount(){if(_pCount != NULL)return *_pCount;return 0;}private:T *_p;int *_pCount;
};void FunTest()
{SharedPtr<int> sp = new int(1);SharedPtr<int> sp1(sp);SharedPtr<int> sp2;sp = sp1;sp2 = sp1;cout<<sp.UseCount()<<endl;cout<<sp1.UseCount()<<endl;cout<<sp2.UseCount()<<endl;
}int main()
{FunTest();return 0;
}
運行結果:
此版本雖然實現了共享,但也存在著不少問題,比如線程安全問題、循環引用問題,定置刪除器問題等。這幾個問題我們下一篇再詳細解說哦。
希望大家可以對博客提出寶貴意見,歡迎來訪哦。