1. 程式人生 > >理解C++智慧指標

理解C++智慧指標

C++智慧指標是面試中經常會問到的一個經典知識點,本身使用也具有很大的意義。本文從下面三個方面對智慧指標的內容進行整理,以期對智慧指標能夠有一個較為清晰的認識:
1 智慧指標的實現原理
2 常用的智慧指標
3 智慧指標的實現

1、智慧指標的實現原理:
智慧指標是一個類,且這個類是個模板類,為了適應不同基本型別的需求,它在建構函式中傳入一個普通指標,將這個基本型別指標封裝為類物件指標,並在解構函式中釋放這個指標,刪除該指標指向的記憶體空間。因為一般使用時,智慧指標的類都是區域性物件,所以當函式(或程式)自動結束時會自動被釋放。

2、 常用的智慧指標:
STL一共提供了四種智慧指標:auto_ptr,unique_ptr,shared_ptr和weak_ptr

對於所有的智慧指標需要注意以下幾點
1) 所有的智慧指標類都有一個explicit建構函式(阻止不應該允許的經過轉換建構函式進行的隱式轉換的發生,即不能在隱式轉換中使用),因此不能直接將指標轉換為只能指標物件,必須顯式呼叫,即

int* test=new int(2);
std::shared_ptr<int> p1=test;    //編譯報錯,無法從“int *”轉換                
                                  //為“std::tr1::shared_ptr<_Ty>”
                                  //因為建構函式被宣告為explicit,必須顯式呼叫
std::shared_ptr<int> p2(test); //正確,顯式呼叫

2) 對全部三種智慧指標都應避免的一點:

string vacation("hello,world"); 
shared_ptr<string> pvac(&vacation); // 錯誤,No pvac過期時,程式將把delete運算子用於非堆記憶體,導致錯誤
shared_ptr<string> pvac1(new string("hello,world"));  //正確

下面簡要說明四種智慧指標的特點:

1)std::auto_ptr:屬於獨佔記憶體的方式,當p1=p2;時,p2的記憶體使用權轉移給p1(p1指向p2之前所指向的地址),p2成為空懸指標(指標地址為0),若之後使用p2,可以編譯通過,但執行時會出現記憶體訪問錯誤,不安全,會出現記憶體崩潰的問題。也因此不能被放入容器中(C++11已將其摒棄)

int* test=new int(2);
    std::auto_ptr<int> p1(new int(5));
    printf("p1:%d\n",p1);
    std::auto_ptr<int> p2(test);
    std::auto_ptr<int> p3=p1;    //不報編譯錯誤
    printf("p3:%d\n",p3);
    printf("p1:%d\n",p1);
    //std::auto_ptr<int> p4(p2);   //執行時報訪問錯誤
    //printf("%d\n",*p1);   //報訪問錯誤,因為p1將記憶體管理權轉移給p3了,p1懸空
    printf("%d\n",*p2);
這裡寫圖片描述

2)C++引入的unique_ptr,也屬於獨享記憶體所有權,但優於auto_ptr,拷貝建構函式和賦值函式只有宣告沒有定義,且為私有函式,不能使用。直接賦值會編譯出錯。需要賦值的時候用std::move

std::unique_ptr<int> p5(new int(8));
    std::unique_ptr<int> p6(test);
    printf("%d\n",*p5);
    printf("%d\n",*p6);
    //std::unique_ptr<int> p7(p5);   //編譯報錯,庫內為private成員,且只宣告,未定義
    //std::unique_ptr<int> p8=p6;    //編譯報錯,庫內為private成員,且只宣告,未定義
    //printf("%d\n",*p7);
    //printf("%d\n",*p8);
    std::unique_ptr<int> p9=unique_ptr<int>(test);
    printf("%d\n",*p9);
    printf("p9:%d\n",p9);
    p6=std::move(p9);
    printf("p6:%d\n",p6);
    printf("p9:%d\n",p9);
    //printf("%d\n",*p6);    //記憶體訪問出錯,因為p6轉移了記憶體所有權
這裡寫圖片描述

3)shared_ptr(boost、C++11)屬於共享記憶體,內部有引用計數機制(實現方式有兩種,一種是輔助類,一種是控制代碼類),對一個記憶體物件進行引用計數,當刪除其中一個指向該記憶體的指標時,引用計數減1,但並不會釋放該記憶體物件,只有當該記憶體物件的引用計數減為0時,才會釋放該塊記憶體,避免了指標空懸、記憶體訪問錯誤的情況。
std::shared_ptr<int> p10(new int(10));
    std::shared_ptr<int> p11(test);
    printf("%d\n",*p10);
    printf("%d\n",*p11);
    printf("p11->use_count:%d\n",p11.use_count());
    std::shared_ptr<int> p12(p10);   //編譯正常
    std::shared_ptr<int> p13=p11;    //編譯正常
    //std::weak_ptr<int> p13=p11;    //編譯正常
    printf("p11->use_count:%d\n",p11.use_count());
    printf("p13->use_count:%d\n",p13.use_count());
    printf("p11:%d\n",p11);
    printf("p13:%d\n",p13);
    //p11=p13;    //編譯正常
    printf("p11->use_count:%d\n",p11.use_count());
    printf("p13->use_count:%d\n",p13.use_count());
    printf("p11:%d\n",p11);
    printf("p13:%d\n",p13);
    //std::shared_ptr<int> p13=test;    //編譯報錯,無法從“int *”轉換為“std::tr1::shared_ptr<_Ty>”,因為建構函式被宣告為explicit,必須顯式呼叫
    printf("%d\n",*p11);
    printf("%d\n",*p12);
    //printf("%d\n",*p13);
    std::shared_ptr<int> p14=std::move(p10);
    printf("%d\n",*p14);
    p10 = p14;
    printf("%d\n",*p10);    //記憶體訪問出錯,因為p10轉移了記憶體所有權
    string vacation("I wandered lonely as a cloud.");
    shared_ptr<string> pvac(&vacation);   // No
    cout<<*pvac;
這裡寫圖片描述

4)weak_ptr(boost、C++11)為shared_ptr設計,弱引用。只對shared_ptr進行引用,但不改變其計數;所以,當被引用的shared_ptr失效後,相應的weak_ptr也會失效。測試程式碼如下:
std::shared_ptr<int> p10(new int(10));
    std::shared_ptr<int> p11(test);
    printf("%d\n",*p10);
    printf("%d\n",*p11);
    printf("p11->use_count:%d\n",p11.use_count());
    std::shared_ptr<int> p12(p10);   //編譯正常
    //std::shared_ptr<int> p13=p11;    //編譯正常
    std::weak_ptr<int> p13=p11;    //編譯正常
    printf("p11->use_count:%d\n",p11.use_count());
    printf("p13->use_count:%d\n",p13.use_count());
    printf("p11:%d\n",p11);
    printf("p13:%d\n",p13);
    //p11=p13;    //編譯正常
    printf("p11->use_count:%d\n",p11.use_count());
    printf("p13->use_count:%d\n",p13.use_count());
    printf("p11:%d\n",p11);
    printf("p13:%d\n",p13);
    //std::shared_ptr<int> p13=test;    //編譯報錯,無法從“int *”轉換為“std::tr1::shared_ptr<_Ty>”,因為建構函式被宣告為explicit,必須顯式呼叫
    printf("%d\n",*p11);
    printf("%d\n",*p12);
    //printf("%d\n",*p13);
    std::shared_ptr<int> p14=std::move(p10);
    printf("%d\n",*p14);
    p10 = p14;
    printf("%d\n",*p10);    //記憶體訪問出錯,因為p10轉移了記憶體所有權
    string vacation("I wandered lonely as a cloud.");
    shared_ptr<string> pvac(&vacation);   // No
    cout<<*pvac;
這裡寫圖片描述

看起來weak_ptr沒有什麼實質的作用,但實際上weak_ptr 主要用在軟體架構設計中,可以在基類(此處的基類並非抽象基類,而是指繼承於抽象基類的虛基類)中定義一個 boost::weak_ptr,用於指向子類的boost::shared_ptr,這樣基類僅僅觀察自己的 boost::weak_ptr 是否為空就知道子類有沒對自己賦值了,而不用影響子類 boost::shared_ptr 的引用計數,用以降低複雜度,更好的管理物件

3、 智慧指標的實現
這裡採用上文提到的輔助類的方式,具體程式碼如下:

//輔助類,用來引用計數
template<class friendclass,class T>
class U_ptr    
{
    friend friendclass;
    T *_p;
    int use_count;
    U_ptr(T *p):_p(p),use_count(1)
    {
        cout<<"U_ptr constructor called!"<<endl;
    }
    ~U_ptr()
    {
        if(use_count==0)
            delete _p;
        cout<<"U_ptr destructor has called!"<<endl;
    }
};

template<class T>
class smartpointer
{
private:
    U_ptr<smartpointer,T>* _ptr;
public:
    smartpointer(T* ptr):_ptr(new U_ptr<smartpointer,T>(ptr))  //建構函式
    {
        cout<<"smartpointer constructor called!"<<endl;
    }
    T& operator *()    //過載*運算子
    {
        return *(_ptr->_p);
    }
    smartpointer& operator=(const smartpointer &sptr)   //過載=運算子
    {
        ++(sptr._ptr->use_count);      //在減少左運算元的使用計數之前使rhs的使用計數加1,是為了防止自身賦值
        if(--_ptr->use_count==0)
            delete _ptr;
        _ptr=sptr._ptr;
        return *this;
    }
    smartpointer(const smartpointer &sptr):_ptr(sptr._ptr)   //拷貝建構函式
    {
        ++_ptr->use_count;
        cout<<"smartpointer copy constructor called!"<<endl;
    }
    int use_count()   //獲取引用計數值
    {
        return _ptr->use_count;
    }
    T* get_ptr()    //獲取指標地址
    {
        return _ptr->_p;
    }
    ~smartpointer()   //解構函式,計數為0時才釋放指標指向的記憶體
    {
        cout<<_ptr->_p<<" smartpointer deconstructor called!"<<endl;
        cout<<"before destruction use_count is="<<use_count()<<endl;
        if(--_ptr->use_count==0)
            delete _ptr;
    }
};
int test()
{
    smartpointer<int> p1(new int(8));//a should only be use to construct once  
    cout<<"p1.use_count="<<p1.use_count()<<endl;
    cout<<"p1="<<p1.get_ptr()<<endl;
    smartpointer<int> p2(p1);  
    cout<<"p1.use_count="<<p1.use_count()<<endl;
    cout<<"p2.use_count="<<p2.use_count()<<endl;
    cout<<"p1="<<p1.get_ptr()<<endl;
    cout<<"p2="<<p2.get_ptr()<<endl;
    smartpointer<int> p3(p1); 
    cout<<"p1.use_count="<<p1.use_count()<<endl;
    cout<<"p3.use_count="<<p3.use_count()<<endl;
    cout<<"p1="<<p1.get_ptr()<<endl;
    cout<<"p3="<<p3.get_ptr()<<endl;
    smartpointer<int> p4(p3);
    cout<<"p3.use_count="<<p3.use_count()<<endl;
    cout<<"p4.use_count="<<p4.use_count()<<endl;
    cout<<"p3="<<p3.get_ptr()<<endl;
    cout<<"p4="<<p4.get_ptr()<<endl;
    p4 = p4; 
    cout<<"p4.use_count="<<p4.use_count()<<endl;
    p4 = p3;  
    cout<<"p4.use_count="<<p4.use_count()<<endl;
    cout<<"p3.use_count="<<p3.use_count()<<endl;
    cout<<"p4="<<p4.get_ptr()<<endl;
    cout<<"p3="<<p3.get_ptr()<<endl;
    return 0;
}
int main(){
    test(); 
    return 0;
}

執行結果如下:

這裡寫圖片描述

與實際應用STL中的智慧指標結果相近

相關推薦

理解C++智慧指標

C++智慧指標是面試中經常會問到的一個經典知識點,本身使用也具有很大的意義。本文從下面三個方面對智慧指標的內容進行整理,以期對智慧指標能夠有一個較為清晰的認識: 1 智慧指標的實現原理 2 常用的智慧指標 3 智慧指標的實現 1、智慧指標的實現

c++智慧指標理解

寫在前面: 智慧指標的運用是c++裡很重要的一個方面。 主要的理解: 智慧指標是為了更容易的動態的管理和使用動態記憶體而設計的,新的標準庫提供兩種智慧指標。 一個是shared_ptr 允許多個指標指向同一個物件 一個是unique_ptr 獨佔所

1.1 c/c++智慧指標

c/c++智慧指標 (直接上程式碼) template <typename T> class sharePtr { public: sharePtr() { instance_ = new T(); } ~sharePtr() { delete instan

C++ 智慧指標詳解

C++ 智慧指標詳解   一、簡介 由於 C++ 語言沒有自動記憶體回收機制,程式設計師每次 new 出來的記憶體都要手動 delete。程式設計師忘記 delete,流程太複雜,最終導致沒有 de

【轉載】C++ 智慧指標(shared_ptr/weak_ptr)原始碼分析

發現一篇對C++11智慧指標分析很透徹的文章,特轉載備忘! 以下轉載自:https://blog.csdn.net/ithiker/article/details/51532484?utm_source=blogxgwz1   C++11目前已經引入了unique_ptr, shared_pt

正確理解C語言指標中的 &a+1,假設a為一個數組

1.int a[5]={1,2,3,4,5}; int p=(int)(&a+1); printf("%d",*(p-1)); 答案為什麼是5? 這個問題的關鍵是理解 &a a是一個數組名,也就是陣列的首地址。 對a進行取地址運算子,得到的是一個指向陣列

C++ 智慧指標(一)

  記憶體安全     在C++中,動態記憶體的管理是通過一對運算子來完成的:new,在動態記憶體中為物件分配空間並返回一個指向該物件的指標,我們可以選擇對物件來進行初始化;delete,接收一個動態物件的指標,銷燬該物件,並釋放與之關聯的記憶體。   動態記憶體的使用很容易出問題,因為確保

C++智慧指標的簡單剖析

導讀 最近在補看《C++ Primer Plus》第六版,這的確是本好書,其中關於智慧指標的章節解析的非常清晰,一解我以前的多處困惑。看開源的C++專案時,也能隨處看到智慧指標的影子。 下面是我在看智慧指標時所做的筆記,希望能夠解決你對智慧指標的一些困擾。 目錄 智慧指標背後的

C++智慧指標:shared_ptr,uniqe_ptr,weak_ptr

動態記憶體 在C++中,動態記憶體的管理是通過一對運算子來完成的:new和delete。 new:在動態記憶體中為物件分配空間,並返回一個指向該物件的指標 delete:接受一個動態物件的指標,銷燬該物件,並釋放與之關聯的記憶體 動態記憶體的使用需要十分小心,因為要在程式設計的時候要

C++智慧指標之unique_ptr

從C++智慧指標之auto_ptr一文中得知:在使用auto_ptr時,可能會不經意的將多個auto_ptr指向同一塊記憶體,造成auto_ptr銷燬釋放時多次釋放同一塊記憶體。為了解決該問題,本文引出了unique_ptr。 顧名思義,unique是唯一的意思。說明它跟auto_p

C++智慧指標之auto_ptr

標頭檔案<memory> 總結為一句話:auto_ptr是獨佔指標,它的出現是能夠自動析構動態分配的記憶體,避免記憶體洩漏,但是auto_ptr有很多弊端,下面會通過示例和講解一一將弊端和用法展現出來。 auto_ptr不能初始化為指向非動態記憶體(原因

C++智慧指標簡單剖析

導讀 《C++ Primer Plus》第六版,其中關於智慧指標的章節解析的非常清晰。C++面試過程中,很多面試官都喜歡問智慧指標相關的 問題,比如你知道哪些智慧指標?shared_ptr的設計原理是什麼?如果讓你自己設計一個智慧指標,你如何完成?等等……。而且在看開源的C

c++智慧指標unqiue_ptr

基本分類 在C++中智慧指標有四種,分別是auto_ptr,shared_ptr,unique_ptr,weak_ptr。其中auto_ptr已經被捨棄,不在累述。大約可以分為兩類: 一種是獨佔式擁有

C++智慧指標shared_ptr講解與使用

手動管理的弊端 在簡單的程式中,我們不大可能忘記釋放 new 出來的指標,但是隨著程式規模的增大,我們忘了 delete 的概率也隨之增大。在 C++ 中 new 出來的指標,賦值意味著引用的傳遞,當賦值運算子同時展現出“值拷貝”和“引用傳遞”兩種截然不同的語義

C++智慧指標auto_ptr使用講解

auto_ptr是C++98標準庫提供的一個智慧指標,但已被C++11明確宣告不再支援。 auto_ptr具有以下缺陷: auto_ptr有拷貝語義,拷貝後源物件變得無效,這可能引發很嚴重的問題;而unique_ptr則無拷貝語義,但提供了移動語義,這樣的錯誤

C++ 智慧指標(unique_ptr, shared_ptr)的原始碼分析

shared_ptr 標頭檔案 template <typename T> class SharedPointer { public: SharedPointer(T *ptr = nullptr, const std::function<void

c++智慧指標學習

1.標頭檔案: #include<memory> 2.要領: 注意我們訪問auto_ptr的成員函式時用的是“.” 3. auto prt ?c++呼叫getxxx()方法返回引用,是否破壞了安全性 #include <iostream> #inclu

c/c++智慧指標總結

  編寫時間: 2018年10月25日12:43:11   在這裡想說一下,智慧指標的順序是通過我看到原始碼的順序來整理的。 1. boost::scoped_ptr // 這些程式碼是從別的部落格上摘取下來的 namespace boost { te

C++——智慧指標

你可能經常性的幹如下的事情。 void remodel(std::string & str) { std::string *ps = new std::string(str); str = ps; return; } 其實你也不願

c++智慧指標

靜態記憶體和棧記憶體。靜態記憶體用來儲存區域性的static物件、類的static資料成員以及定義在任何函式之外的變數。棧記憶體用來儲存定義在函式內的非static的物件。分配在靜態或者棧記憶體中的物件由編譯器自動建立和銷燬。 對於棧物件,僅在其定義的程式執行時