C++-智能指针详解
引言
除了靜態內存和棧內存外,每個程序還有一個內存池,這部分內存被稱為自由空間或者堆。程序用堆來存儲動態分配的對象即那些在程序運行時分配的對象,當動態對象不再使用時,我們的代碼必須顯式的銷毀它們。
在C++中,動態內存的管理是用一對運算符完成的:new和delete,new:在動態內存中為對象分配一塊空間并返回一個指向該對象的指針,delete:指向一個動態獨享的指針,銷毀對象,并釋放與之關聯的內存。
動態內存管理經常會出現兩種問題:一種是忘記釋放內存,會造成內存泄漏;一種是尚有指針引用內存的情況下就釋放了它,就會產生引用非法內存的指針。
為了更加容易(更加安全)的使用動態內存,引入了智能指針的概念。智能指針的行為類似常規指針,重要的區別是它負責自動釋放所指向的對象。標準庫提供的兩種智能指針的區別在于管理底層指針的方法不同,shared_ptr允許多個指針指向同一個對象,unique_ptr則“獨占”所指向的對象。標準庫還定義了一種名為weak_ptr的伴隨類,它是一種弱引用,指向shared_ptr所管理的對象,這三種智能指針都定義在memory頭文件中。
?
什么是智能指針
C++的智能指針其實就是對普通指針的封裝(即封裝成一個類),通過重載 * 和 ->兩個運算符,使得智能指針表現的就像普通指針一樣。
智能指針的作用
C++程序設計中使用堆內存是非常頻繁的操作,堆內存的申請和釋放都由程序員自己管理。程序員自己管理堆內存可以提高程序的效率,但是整體來說堆內存的管理是麻煩的,C++11中引入了智能指針的概念,方便管理堆內存。使用普通指針,容易造成堆內存泄露(忘記釋放),二次釋放,程序發生異常時內存泄露等問題等,使用智能指針能更好的管理堆內存。
智能指針實現原理
智能指針(smart pointer)的通用實現技術是使用引用計數(reference count)。智能指針類將一個計數器與類指向的對象相關聯,引用計數跟蹤該類有多少個對象的指針指向同一對象。每次創建類的新對象時,初始化指針就將引用計數置為1;當對象作為另一對象的副本而創建時,拷貝構造函數拷貝指針并增加與之相應的引用計數;對一個對象進行賦值時,賦值操作符減少左操作數所指對象的引用計數(如果引用計數為減至0,則刪除對象),并增加右操作數所指對象的引用計數;調用析構函數時,析構函數減少引用計數(如果引用計數減至0,則刪除基礎對象)。
使用C++標準庫中的智能指針
智能指針在C++11版本之后提供,包含在頭文件<memory>中,廢棄了auto_ptr,從Boost標準庫中引入了shared_ptr、unique_ptr、weak_ptr三種指針。
STL一共給我們提供了四種智能指針:auto_ptr、unique_ptr、shared_ptr和weak_ptr,auto_ptr是C++98提供的解決方案,C+11已將其摒棄,并提出了unique_ptr作為auto_ptr替代方案。雖然auto_ptr已被摒棄,但在實際項目中仍可使用,但建議使用較新的unique_ptr,因為unique_ptr比auto_ptr更加安全,后文會詳細敘述。shared_ptr和weak_ptr則是C+11從準標準庫Boost中引入的兩種智能指針。此外,Boost庫還提出了boost::scoped_ptr、boost::scoped_array、boost::intrusive_ptr 等智能指針,雖然尚未得到C++標準采納,但是實際開發工作中可以使用。
- shared_ptr
利用引用計數->每有一個指針指向相同的一片內存時,引用計數+1,每當一個指針取消指向一片內存時,引用計數-1,減為0時釋放內存。
- week_ptr
????????????弱指針 ->輔助shared_ptr解決循環引用的問題
????????????weak_ptr是為了配合shared_ptr而引入的一種智能指針,因為它不具有普通指針的行為,沒有重載operator*和->,它的最大作用在于協助shared_ptr工作,像旁觀者那樣觀測資源的使用情況。weak_ptr可以從一個shared_ptr或者另一個weak_ptr對象構造,獲得資源的觀測權。但weak_ptr沒有共享資源,它的構造不會引起指針引用計數的增加。使用weak_ptr的成員函數use_count()可以觀測資源的引用計數,另一個成員函數expired()的功能等價于use_count()==0,但更快,表示被觀測的資源 (也就是shared_ptr的管理的資源)已經不復存在。weak_ptr可以使用一個非常重要的成員函數lock()從被觀測的shared_ptr 獲得一個可用的shared_ptr對象, 從而操作資源。但當expired()==true的時候,lock()函數將返回一個存儲空指針的shared_ptr。
?
- unique_ptr
? ? ? ? ? ?“唯一”擁有其所指對象,同一時刻只能有一個unique_ptr指向給定對象(禁止拷貝、賦值),可以釋放所有權,轉移所有權。
智能指針的實現(繼承引用計數基類,實現線程安全)
#include<iostream> #include<mutex> using namespace std;/* 實現一個線程安全的智能指針 *//* 引用計數基類 */ class Sp_counter {private :size_t *_count;std::mutex mt;public :Sp_counter(){cout<<"父類構造,分配counter內存"<<endl;_count = new size_t(0);}virtual ~Sp_counter(){if(_count && !(*_count) ){cout<<"父類析構"<<endl;cout<<"[釋放counter內存]"<<endl;delete _count;_count = NULL;}}Sp_counter &operator=(Sp_counter &spc){cout<<"父類重載="<<endl;cout<<"[釋放counter內存]"<<endl;delete _count;this->_count = spc._count;return *this;}Sp_counter &GetCounter(){return *this;}size_t Get_Reference(){return *_count;}virtual void Increase(){mt.lock();(*_count)++;//cout<<"_count++:"<<*_count<<endl;mt.unlock();}virtual void Decrease(){mt.lock();(*_count)--;//cout<<"_count--:"<<*_count<<endl;mt.unlock();} };template<typename T> class smart_pointer : public Sp_counter {private :T *_ptr;public :smart_pointer(T *ptr = NULL); ~smart_pointer(); smart_pointer(smart_pointer<T> &);smart_pointer<T> &operator=(smart_pointer<T> &);T &operator*();T *operator->(void);size_t use_count();};/* 子類參構造函數&帶參數構造函數 */ template<typename T> inline smart_pointer<T>::smart_pointer(T *ptr) {if(ptr){cout<<"子類默認構造"<<endl;_ptr = ptr;this->Increase();} } /* 子類析構函數 */ template<typename T> smart_pointer<T>::~smart_pointer() {/* 指針非空才析構 */if(this->_ptr){cout<<"子類析構,計數減1"<<endl;if(this->Get_Reference())this->Decrease();if(!(this->Get_Reference()) ){cout<<"(((子類析構,主內存被釋放)))"<<endl;delete _ptr;_ptr = NULL;}} }/* 得到引用計數值 */ template<typename T> inline size_t smart_pointer<T>::use_count() {return this->Get_Reference(); }/* 拷貝構造 */ template<typename T> inline smart_pointer<T>::smart_pointer(smart_pointer<T> &sp) {cout<<"子類拷貝構造"<<endl;/* 防止自己對自己的拷貝 */if(this != &sp){this->_ptr = sp._ptr;this->GetCounter() = sp.GetCounter();this->Increase();}} /* 賦值構造 */ template<typename T> inline smart_pointer<T> &smart_pointer<T>::operator=(smart_pointer<T> &sp) {/* 防止自己對自己的賦值以及指向相同內存單元的賦值 */if(this != &sp){cout<<"賦值構造"<<endl;/* 如果不是構造一個新智能指針并且兩個只能指針不是指向同一內存單元 *//* =左邊引用計數減1,=右邊引用計數加1 */if(this->_ptr && this->_ptr != sp._ptr){this->Decrease();/* 引用計數為0時 */if(!this->Get_Reference()){cout<<"引用計數為0,主動調用析構"<<endl;this->~smart_pointer();//this->~Sp_counter();cout<<"調用完畢"<<endl;}}this->_ptr = sp._ptr;this->GetCounter() = sp.GetCounter();this->Increase();}return *this; }/* 重載解引用*運算符 */ template<typename T> inline T &smart_pointer<T>::operator*() {return *(this->_ptr); } template<typename T> inline T *smart_pointer<T>::operator->(void) {return this->_ptr; }int main() {int *a = new int(10);int *b = new int(20);cout<<"-------------默認構造測試----------------->"<<endl;cout<<"構造sp"<<endl;smart_pointer<int> sp(a);cout<<"sp.use_count:"<<sp.use_count()<<endl;cout<<"------------------------------------------>"<<endl<<endl;{cout<<"-------------拷貝構造測試----------------->"<<endl;cout<<"構造sp1 :sp1(sp)"<<endl;smart_pointer<int> sp1(sp);cout<<"構造sp2 :sp2(sp)"<<endl;smart_pointer<int> sp2(sp1);cout<<"sp1和sp2引用計數為3才是正確的"<<endl;cout<<"sp1.use_count:"<<sp1.use_count()<<endl;cout<<"sp2.use_count:"<<sp2.use_count()<<endl;cout<<"------------------------------------------>"<<endl<<endl;cout<<"調用析構釋放sp1,sp2"<<endl;}cout<<"-------------析構函數測試----------------->"<<endl;cout<<"此處sp.use_count應該為1才是正確的"<<endl;cout<<"sp.use_count:"<<sp.use_count()<<endl;cout<<"------------------------------------------>"<<endl<<endl;cout<<"-------------賦值構造測試----------------->"<<endl;cout<<"構造sp3 :sp3(b)"<<endl;smart_pointer<int> sp3(b);cout<<"sp3.use_count:"<<sp3.use_count()<<endl;cout<<"sp3 = sp"<<endl;sp3 = sp;cout<<"sp3先被釋放,然后sp3引用計數為2才正確,sp的引用計數為2才正確"<<endl;cout<<"sp3.use_count:"<<sp3.use_count()<<endl;cout<<"sp.use_count :"<<sp.use_count()<<endl;cout<<"------------------------------------------>"<<endl<<endl;cout<<"-------------解引用測試----------------->"<<endl;cout<<"*sp3:"<<*sp3<<endl;cout<<"*sp3 = 100"<<endl;*sp3 = 100;cout<<"*sp3:"<<*sp3<<endl;cout<<"------------------------------------------>"<<endl;// cout<<"sp3.use_count:"<<sp3.use_count()<<endl;//cout<<"sp.use_count:"<<sp.use_count()<<endl;cout<<"===================end main===================="<<endl;return 0; }上面的實現是通過繼承引用計數基類實現的智能指針,把引用計數寫到智能指針類里,也可實現。
下面是一個基于引用計數的智能指針的實現,需要實現構造,析構,拷貝構造,=操作符重載,重載*-和>操作符。代碼如下,
template <typename T> class SmartPointer { public://構造函數SmartPointer(T* p=0): _ptr(p), _reference_count(new size_t){if(p)*_reference_count = 1; else*_reference_count = 0; }//拷貝構造函數SmartPointer(const SmartPointer& src) {if(this!=&src) {_ptr = src._ptr;_reference_count = src._reference_count;(*_reference_count)++;}}//重載賦值操作符SmartPointer& operator=(const SmartPointer& src) {if(_ptr==src._ptr) {return *this;}releaseCount();_ptr = src._ptr;_reference_count = src._reference_count;(*_reference_count)++;return *this;}//重載操作符T& operator*() {if(ptr) {return *_ptr;}//throw exception}//重載操作符T* operator->() {if(ptr) {return _ptr;}//throw exception}//析構函數~SmartPointer() {if (--(*_reference_count) == 0) {delete _ptr;delete _reference_count;}} private:T *_ptr;size_t *_reference_count;void releaseCount() {if(_ptr) {(*_reference_count)--;if((*_reference_count)==0) {delete _ptr;delete _reference_count;}}} };int main() {SmartPointer<char> cp1(new char('a'));SmartPointer<char> cp2(cp1);SmartPointer<char> cp3;cp3 = cp2;cp3 = cp1;cp3 = cp3;SmartPointer<char> cp4(new char('b'));cp3 = cp4; }總結
1.答案:智能指針是一個類,這個類的構造函數中傳入一個普通指針,析構函數中釋放傳入的指針。智能指針的類都是棧上的對象,所以當函數(或程序)結束時會自動被釋放,
2.最常用的智能指針:?
??????????????1)std::auto_ptr,有很多問題。 不支持復制(拷貝構造函數)和賦值(operator =),但復制或賦值的時候不會提示出錯。因為不能被復制,所以不能被放入容器中。
??????????????2) C++11引入的unique_ptr, 也不支持復制和賦值,但比auto_ptr好,直接賦值會編譯出錯。實在想賦值的話,需要使用:std::move。
???????????????例如:
????????????????????std::unique_ptr<int> p1(new int(5));
????????????????????std::unique_ptr<int> p2 = p1; // 編譯會出錯
????????????????????std::unique_ptr<int> p3 = std::move(p1); // 轉移所有權, 現在那塊內存歸p3所有, p1成為無效的指針.
??????????????3)?C++11或boost的shared_ptr,基于引用計數的智能指針??呻S意賦值,直到內存的引用計數為0的時候這個內存會被釋放。但這個模型使用時可能會出現循環引用問題,可以使用weak_ptr輔助解決。
??????????????4)C++11或boost的weak_ptr,弱引用。 引用計數有一個問題就是互相引用形成環,這樣兩個指針指向的內存都無法釋放。需要手動打破循環引用或使用weak_ptr。顧名思義,weak_ptr是一個弱引用,只引用,不計數。如果一塊內存被shared_ptr和weak_ptr同時引用,當所有shared_ptr析構了之后,不管還有沒有weak_ptr引用該內存,內存也會被釋放。所以weak_ptr不保證它指向的內存一定是有效的,在使用之前需要檢查weak_ptr是否為空指針。
? ? ? ? ? ? ? 5)Boost中的scoped_ptr,scoped和weak_ptr的區別就是,給出了拷貝和賦值操作的聲明并沒有給出具體實現,并且將這兩個操作定義成私有的,這樣就保證scoped_ptr不能使用拷貝來構造新的對象也不能執行賦值操作,更加安全,但有了”++”“–”以及“*”“->”這些操作,比weak_ptr能實現更多功能。
前向聲明時的交叉循環引用問題
考慮一個簡單的對象建模——家長與子女:a Parent has a Child, a Child knowshis/her Parent。在Java 里邊很好寫,不用擔心內存泄漏,也不用擔心空懸指針,只要正確初始化myChild 和myParent,那么Java 程序員就不用擔心出現訪問錯誤。一個handle 是否有效,只需要判斷其是否non null。
public class Parent
{
private Child myChild;
}
public class Child
{
private Parent myParent;
}
在C++ 里邊就要為資源管理費一番腦筋。如果使用原始指針作為成員,Child和Parent由誰釋放?那么如何保證指針的有效性?如何防止出現空懸指針?這些問題是C++面向對象編程麻煩的問題,現在可以借助smart pointer把對象語義(pointer)轉變為值(value)語義,shared_ptr輕松解決生命周期的問題,不必擔心空懸指針。但是這個模型存在循環引用的問題,注意其中一個指針應該為weak_ptr。
總結
以上是生活随笔為你收集整理的C++-智能指针详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: intel realsense
- 下一篇: 二进制转八进制转换用计算机,二进制转八进