c++ 智能指針用法詳解

本文介紹c++裏面的四個智能指針: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中後三個是c++11支持,而且第一個已經被c++11棄用。html

爲何要使用智能指針:咱們知道c++的內存管理是讓不少人頭疼的事,當咱們寫一個new語句時,通常就會當即把delete語句直接也寫了,可是咱們不能避免程序還未執行到delete時就跳轉了或者在函數中沒有執行到最後的delete語句就返回了,若是咱們不在每個可能跳轉或者返回的語句前釋放資源,就會形成內存泄露。使用智能指針能夠很大程度上的避免這個問題,由於智能指針就是一個類,當超出了類的做用域是,類會自動調用析構函數,析構函數會自動釋放資源。下面咱們逐個介紹。c++

auto_ptr (官方文檔算法

class Test
{
public:
    Test(string s)
    {
        str = s;
       cout<<"Test creat\n";
    }
    ~Test()
    {
        cout<<"Test delete:"<<str<<endl;
    }
    string& getStr()
    {
        return str;
    }
    void setStr(string s)
    {
        str = s;
    }
    void print()
    {
        cout<<str<<endl;
    }
private:
    string str;
};


int main()
{
    auto_ptr<Test> ptest(new Test("123"));
    ptest->setStr("hello ");
    ptest->print();
    ptest.get()->print();
    ptest->getStr() += "world !";
    (*ptest).print();
    ptest.reset(new Test("123"));
    ptest->print();
    return 0;
}

運行結果以下數組

image

如上面的代碼:智能指針能夠像類的原始指針同樣訪問類的public成員,成員函數get()返回一個原始的指針,成員函數reset()從新綁定指向的對象,而原來的對象則會被釋放。注意咱們訪問auto_ptr的成員函數時用的是「.」,訪問指向對象的成員時用的是「->」。咱們也可用聲明一個空智能指針auto_ptr<Test>ptest();安全

當咱們對智能指針進行賦值時,如ptest2 = ptest,ptest2會接管ptest原來的內存管理權,ptest會變爲空指針,若是ptest2原來不爲空,則它會釋放原來的資源,基於這個緣由,應該避免把auto_ptr放到容器中,由於算法對容器操做時,很難避免STL內部對容器實現了賦值傳遞操做,這樣會使容器中不少元素被置爲NULL。判斷一個智能指針是否爲空不能使用if(ptest == NULL),應該使用if(ptest.get() == NULL),以下代碼                                                           本文地址函數

int main()
{
    auto_ptr<Test> ptest(new Test("123"));
    auto_ptr<Test> ptest2(new Test("456"));
    ptest2 = ptest;
    ptest2->print();
    if(ptest.get() == NULL)cout<<"ptest = NULL\n";
    return 0;
}

image

還有一個值得咱們注意的成員函數是release,這個函數只是把智能指針賦值爲空,可是它原來指向的內存並無被釋放,至關於它只是釋放了對資源的全部權,從下面的代碼執行結果能夠看出,析構函數沒有被調用。優化

int main()
{
    auto_ptr<Test> ptest(new Test("123"));
    ptest.release();
    return 0;
}

image

那麼當咱們想要在中途釋放資源,而不是等到智能指針被析構時才釋放,咱們可使用ptest.reset(); 語句。spa


unique_ptr (官方文檔) .net

 

 

unique_ptr,是用於取代c++98的auto_ptr的產物,在c++98的時候尚未移動語義(move semantics)的支持,所以對於auto_ptr的控制權轉移的實現沒有核心元素的支持,可是仍是實現了auto_ptr的移動語義,這樣帶來的一些問題是拷貝構造函數和複製操做重載函數不夠完美,具體體現就是把auto_ptr做爲函數參數,傳進去的時候控制權轉移,轉移到函數參數,當函數返回的時候並無一個控制權移交的過程,因此過了函數調用則原先的auto_ptr已經失效了.在c++11當中有了移動語義,使用move()把unique_ptr傳入函數,這樣你就知道原先的unique_ptr已經失效了.移動語義自己就說明了這樣的問題,比較坑爹的是標準描述是說對於move以後使用原來的內容是未定義行爲,並不是拋出異常,因此仍是要靠人肉遵照遊戲規則.再一個,auto_ptr不支持傳入deleter,因此只能支持單對象(delete object),而unique_ptr對數組類型有偏特化重載,而且還作了相應的優化,好比用[]訪問相應元素等.指針

unique_ptr 是一個獨享全部權的智能指針,它提供了嚴格意義上的全部權,包括:

一、擁有它指向的對象

二、沒法進行復制構造,沒法進行復制賦值操做。即沒法使兩個unique_ptr指向同一個對象。可是能夠進行移動構造和移動賦值操做

三、保存指向某個對象的指針,當它自己被刪除釋放的時候,會使用給定的刪除器釋放它指向的對象

unique_ptr 能夠實現以下功能:

一、爲動態申請的內存提供異常安全

二、講動態申請的內存全部權傳遞給某函數

三、從某個函數返回動態申請內存的全部權

四、在容器中保存指針

五、auto_ptr 應該具備的功能

unique_ptr<Test> fun()
{
    return unique_ptr<Test>(new Test("789"));
}
int main()
{
    unique_ptr<Test> ptest(new Test("123"));
    unique_ptr<Test> ptest2(new Test("456"));
    ptest->print();
    ptest2 = std::move(ptest);//不能直接ptest2 = ptest
    if(ptest == NULL)cout<<"ptest = NULL\n";
    Test* p = ptest2.release();
    p->print();
    ptest.reset(p);
    ptest->print();
    ptest2 = fun(); //這裏能夠用=,由於使用了移動構造函數
    ptest2->print();
    return 0;
}

image

unique_ptr 和 auto_ptr用法很類似,不過不能使用兩個智能指針賦值操做,應該使用std::move; 並且它能夠直接用if(ptest == NULL)來判斷是否空指針;release、get、reset等用法也和auto_ptr一致,使用函數的返回值賦值時,能夠直接使用=, 這裏使用c++11 的移動語義特性。另外注意的是當把它當作參數傳遞給函數時(使用值傳遞,應用傳遞時不用這樣),傳實參時也要使用std::move,好比foo(std::move(ptest))。它還增長了一個成員函數swap用於交換兩個智能指針的值


share_ptr (官方文檔)

從名字share就能夠看出了資源能夠被多個指針共享,它使用計數機制來代表資源被幾個指針共享。能夠經過成員函數use_count()來查看資源的全部者個數。出了能夠經過new來構造,還能夠經過傳入auto_ptr, unique_ptr,weak_ptr來構造。當咱們調用release()時,當前指針會釋放資源全部權,計數減一。當計數等於0時,資源會被釋放。具體的成員函數解釋能夠參考 here

int main()
{
    shared_ptr<Test> ptest(new Test("123"));
    shared_ptr<Test> ptest2(new Test("456"));
    cout<<ptest2->getStr()<<endl;
    cout<<ptest2.use_count()<<endl;
    ptest = ptest2;//"456"引用次數加1,「123」銷燬
    ptest->print();
    cout<<ptest2.use_count()<<endl;//2
    cout<<ptest.use_count()<<endl;//2
    ptest.reset();
    ptest2.reset();//此時「456」銷燬
    cout<<"done !\n";
    return 0;
}

image


weak_ptr(官方文檔)

weak_ptr是用來解決shared_ptr相互引用時的死鎖問題,若是說兩個shared_ptr相互引用,那麼這兩個指針的引用計數永遠不可能降低爲0,資源永遠不會釋放。它是對對象的一種弱引用,不會增長對象的引用計數,和shared_ptr之間能夠相互轉化,shared_ptr能夠直接賦值給它,它能夠經過調用lock函數來得到shared_ptr。

class B;
class A
{
public:
    shared_ptr<B> pb_;
    ~A()
    {
        cout<<"A delete\n";
    }
};
class B
{
public:
    shared_ptr<A> pa_;
    ~B()
    {
        cout<<"B delete\n";
    }
};

void fun()
{
    shared_ptr<B> pb(new B());
    shared_ptr<A> pa(new A());
    pb->pa_ = pa;
    pa->pb_ = pb;
    cout<<pb.use_count()<<endl;
    cout<<pa.use_count()<<endl;
}

int main()
{
    fun();
    return 0;
}

image

能夠看到fun函數中pa ,pb之間互相引用,兩個資源的引用計數爲2,當要跳出函數時,智能指針pa,pb析構時兩個資源引用計數會減一,可是二者引用計數仍是爲1,致使跳出函數時資源沒有被釋放(A B的析構函數沒有被調用),若是把其中一個改成weak_ptr就能夠了,咱們把類A裏面的shared_ptr<B> pb_; 改成weak_ptr<B> pb_; 運行結果以下,這樣的話,資源B的引用開始就只有1,當pb析構時,B的計數變爲0,B獲得釋放,B釋放的同時也會使A的計數減一,同時pa析構時使A的計數減一,那麼A的計數爲0,A獲得釋放。

image

注意的是咱們不能經過weak_ptr直接訪問對象的方法,好比B對象中有一個方法print(),咱們不能這樣訪問,pa->pb_->print(); 英文pb_是一個weak_ptr,應該先把它轉化爲shared_ptr,如:shared_ptr<B> p = pa->pb_.lock();    p->print();

 

參考資料

胡健:http://www.cnblogs.com/hujian/archive/2012/12/10/2810776.html

胡健:http://www.cnblogs.com/hujian/archive/2012/12/10/2810754.html

胡健:http://www.cnblogs.com/hujian/archive/2012/12/10/2810785.html

天方:http://www.cnblogs.com/TianFang/archive/2008/09/20/1294590.html

gaa_ra:http://blog.csdn.net/gaa_ra/article/details/7841204

cplusplus:http://www.cplusplus.com/

 

【版權聲明】轉載請註明出處:http://www.cnblogs.com/TenosDoIt/p/3456704.html

相關文章
相關標籤/搜索