C++11 智能指針2019-08-20

智能指針的基本思想:

將堆對象的生存周期用棧對象(智能指針)來管理.當new一個堆對象時,立刻用智能指針來接管,具體的做法是在構(gòu)造函數(shù)中進行初始化(用一個指針指向堆對象),在析構(gòu)函數(shù)中調(diào)用delete來釋放堆對象.

C++11之后,主推三種智能指針:unique_ptr,shared_ptr,weak_ptr.智能指針的出現(xiàn)很大程度上避免了內(nèi)存泄漏這一個問題,不用重復使用new,delete來實現(xiàn)內(nèi)存的獲取和釋放;功能的實現(xiàn)主要是智能指針是一個類,當該類超出了類的作用域之后,會自動調(diào)用析構(gòu)函數(shù),析構(gòu)函數(shù)會自動釋放資源.

  • unique_ptr是通過指針占有并管理另一個對象,在unique_ptr離開作用域時釋放該對象; unique_ptr對象通過運算符*和 ->(對于單個對象)或operator [](對于數(shù)組對象)提供對其管理對象的訪問來實現(xiàn)普通指針功能;且獨享被管理對象,同一時刻只能有一個unique_ptr擁有對象的所有權(quán),當其被賦值時對象的所有權(quán)也發(fā)生轉(zhuǎn)移备蚓,當其被銷毀時被管理對象也自動被銷毀.
  • shared_ptr是通過指針保持對象共享所有權(quán)的智能指針,多個shared_ptr對象可以占有同一個對象;它使用計數(shù)機制來表明資源被多少個指針所共享,可以通過其成員函數(shù)use_count()來查看;多個線程能在shared_ptr不同實例上調(diào)用成員函數(shù)而不附加同步,即使這些實例是副本,且共享同一個對象的所有權(quán).但當多個線程訪問同一個shared_ptr而不設定同步,切任一線程使用shared_ptr的非const成員函數(shù)時,將出現(xiàn)數(shù)據(jù)競爭,解決方法是使用原子操作.還有一個問題是循環(huán)引用的問題:
    如下例:
#include <iostream>
#include <memory>

using namespace std;
class B;

class A
{
public:
    std::shared_ptr<B> b;
    A(){
        cout << "hhyA" << endl;
    }
    ~A()
    {
        cout << "~A()" << endl;
    }
};
class B
{
public:
    shared_ptr<A> a;
    B()
    {
        cout << "hhyB" << endl;
    };
    ~B()
    {
        cout << "~B()" << endl;
    }

};

void test_shared_ptr_cycleRef()
{
    shared_ptr<A>hhyA(new A);
    shared_ptr<B>hhyB(new B);
    hhyA->b = hhyB;
    hhyB->a = hhyA;
    cout << hhyA->b.use_count() << endl;
    cout << hhyB->a.use_count() << endl;
}

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

運行結(jié)果:

hhyA
hhyB
2
2

運行結(jié)果并沒有調(diào)用相應的析構(gòu)函數(shù),原因在于shared_ptr不僅管理著hhyA與hhyB,而且也管理著hhyA中的b,hhyB中的a;當銷毀時hhyA由hhyB中的a管理,hhyB由hhyA中的b管理,也就是說hhyA的釋放取決于hhyB,hhyB的釋放取決于hhyA,相互影響,造成了循環(huán)引用.

  • weak_ptr是對被shared_ptr管理的對象的非擁有性("弱")引用,在其訪問引用的對象前必須先轉(zhuǎn)換為shared_ptr,目的是確定對象是否還存在;weak_ptr用來表達臨時所有權(quán)的概念:當某個對象只有存在時才需要被訪問,而且隨時可能被人刪除時,可以引用weak_ptr來跟蹤該對象;weak_ptr除了解決類之間循環(huán)引用問題,另一用法是打斷shared_ptr所管理對象的環(huán)狀引用問題,可以令該循環(huán)引用中的指針為弱指針以避免此情況.
    解決類之間的循環(huán)引用的運行實例:
#include <iostream>
#include <memory>

using namespace std;
class B;

class A
{
public:
//  std::shared_ptr<B>b;
    std::weak_ptr<B> b;
    A(){
        cout << "hhyA" << endl;
    }
    ~A()
    {
        cout << "~A()" << endl;
    }
};
class B
{
public:
//  shared_ptr<A> a;
    weak_ptr<A> a;
    B()
    {
        cout << "hhyB" << endl;
    };
    ~B()
    {
        cout << "~B()" << endl;
    }

};

void test_shared_ptr_cycleRef()
{
    shared_ptr<A>hhyA(new A);
    shared_ptr<B>hhyB(new B);
        hhyA->b = hhyB;
    hhyB->a = hhyA;
       cout << hhyA->b.use_count() << endl;
    cout << hhyB->a.use_count() << endl;
    
}

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

運行結(jié)果:

hhyA
hhyB
1
1
~B()
~A()

注意:
weak_ptr的使用必須結(jié)合shared_ptr,不能單獨使用;
注意析構(gòu)函數(shù)的順序,誰是weak_ptr對象,就先析構(gòu)誰;
weak_ptr之所以能夠解決循環(huán)引用問題就是在于其不增加也不減少循環(huán)引用次數(shù).
shared_ptr初始化時除了使用new更好的方法是使用make_shared:
如: auto foo = std::make_shared<int>(10);

自己實現(xiàn)一個智能指針(shared_ptr)

\color{blue}{實現(xiàn)思路}

  • 智能指針并不是真正的指針,是對一個真正指針的包裝,代理原指針.
  • 在智能指針類中的析構(gòu)函數(shù)中,可以對原指針進行delete以及賦空指針的操作,在出了作用域之后會自動調(diào)用析構(gòu)函數(shù),
  • 由于實現(xiàn)的shared_ptr,采用計數(shù)的操作來計算指向?qū)ο蟮闹羔槀€數(shù),當多個指針指向同一對象時,最后一個銷毀的智能指針(即計數(shù)器等于1的指針)需要對原指針進行銷毀.
  • 需要拷貝構(gòu)造函數(shù)與拷貝賦值構(gòu)造函數(shù)
#ifndef THIS_SMART_PTR_H
#define THIS_SMART_PTR_H

#include <iostream>
#include <string>

using namespace std;

template <class T>
class smart_ptr
{
public:
    //構(gòu)造函數(shù)
    smart_ptr(T* p);
    //拷貝構(gòu)造函數(shù)
    smart_ptr(const smart_ptr &p);
    //析構(gòu)函數(shù)
    ~smart_ptr();
    //use_count
    int use_count();
    //賦值操作
    smart_ptr& operator=(const smart_ptr &p);
    //指針操作
    T& operator*();
private:
    T* ptr;
    int* p_cnt;

};

template <class T>
smart_ptr<T>::smart_ptr(T* p):ptr(p)
{
    if(ptr)
    {
        p_cnt = new int(1);
    }
    else
        p_cnt = new int(0);
}
template <class T>
smart_ptr<T>::smart_ptr(const smart_ptr &p) {
    if( this != &p){
        ptr = p.ptr;
        p_cnt = p.p_cnt;
        (*p_cnt)++;  //必須加括號,優(yōu)先級符號問題
    }

}

template <class T>
smart_ptr<T>::~smart_ptr()
{
    --(*p_cnt);      //注意--不能放在后面
    //只對最后一個對象引用ptr時才釋放
    if(*p_cnt == 0)
    {
        delete ptr;
        delete p_cnt;
        ptr= nullptr;
        p_cnt = nullptr;
         cout << "final ~smart_ptr" << endl;
    }
    cout << "~smart_ptr" << endl;
}

template <class T>
int smart_ptr<T>::use_count() {
    return *(this->p_cnt);
}
//重載等號函數(shù)不同于復制構(gòu)造函數(shù),即等號左邊的對象可能已經(jīng)指向某塊內(nèi)存,這樣,我們就得先判斷左邊對象指向的內(nèi)存已經(jīng)被引用的次數(shù).如果次數(shù)為1,表明我們可以釋放這塊內(nèi)存蝇狼;反之則不釋放珍语,由其他對象來釋放
template <class T>
smart_ptr<T>& smart_ptr<T>::operator=(const smart_ptr &p) {
    if(this->ptr==p.ptr)
    {
        return *this;      //檢測自我賦值
    };
    if(ptr != p.ptr)
    {
        --(*p_cnt);
        // 將左操作數(shù)對象的使用計數(shù)減1,若該對象的使用計數(shù)減至0,則刪除該對象
        if(*(this->p_cnt)==0)
        {
            delete ptr;
            delete p_cnt;

        }
    }

    ptr=p.ptr;
    p_cnt=p.p_cnt;
    ++(*p_cnt);
    return *this;

}
template<class T>
T& smart_ptr<T>::operator *()
{
    return *ptr;
}

對代碼進行測試:

void test_smartptr()
{
        smart_ptr<int>hhyA(new int(10));
    cout << "hhyA.cnt" << hhyA.use_count() << endl;
    smart_ptr<int>hhyB(hhyA);  //拷貝構(gòu)造
    cout << "hhyB.cnt" << hhyA.use_count() << endl;
    smart_ptr<int>hhyC(new int(5));  
    cout << "hhyC.cnt" << hhyC.use_count() << endl;
    hhyC = hhyB;  //拷貝賦值
    cout << "hhyC.cnt" << hhyC.use_count() << endl;
    cout << *hhyA << endl;
}
int main()
{
    test_smartptr();
    return 0;
}

運行結(jié)果:

hhyA.cnt1
hhyB.cnt2
hhyC.cnt1
hhyC.cnt3
10
~smart_ptr
~smart_ptr
 cout << "final ~smart_ptr" << endl;
~smart_ptr

結(jié)果分析:hhyA首先通過構(gòu)造函數(shù)使得*p_cnt等于1,然后hhyB通過拷貝構(gòu)造函數(shù)與hhyA指向同一對象,p_cnt的值又加一,再然后hhyC通過先指向另外一個對象,然后通過賦值構(gòu)造函數(shù),p_cnt的值先減一后再等于hhyA中的p_cnt的值(2)之后,再加一,即值最后等于三.
另外,調(diào)用析構(gòu)函數(shù)也是調(diào)用三次,但只有最后一個銷毀的智能指針會對原指針對象進行銷毀.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末茁计,一起剝皮案震驚了整個濱河市闪幽,隨后出現(xiàn)的幾起案子啥辨,更是在濱河造成了極大的恐慌,老刑警劉巖盯腌,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件委可,死亡現(xiàn)場離奇詭異,居然都是意外死亡腊嗡,警方通過查閱死者的電腦和手機着倾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來燕少,“玉大人卡者,你說我怎么就攤上這事】兔牵” “怎么了崇决?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵材诽,是天一觀的道長。 經(jīng)常有香客問我恒傻,道長脸侥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任盈厘,我火速辦了婚禮睁枕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沸手。我一直安慰自己外遇,他們只是感情好,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布契吉。 她就那樣靜靜地躺著跳仿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捐晶。 梳的紋絲不亂的頭發(fā)上菲语,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天,我揣著相機與錄音惑灵,去河邊找鬼谨究。 笑死,一個胖子當著我的面吹牛泣棋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播畔塔,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼潭辈,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了澈吨?” 一聲冷哼從身側(cè)響起把敢,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎谅辣,沒想到半個月后修赞,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡桑阶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年柏副,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蚣录。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡割择,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出萎河,到底是詐尸還是另有隱情荔泳,我是刑警寧澤蕉饼,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站玛歌,受9級特大地震影響贮预,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜创葡,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一迎献、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧译荞,春花似錦瓤的、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至篙骡,卻和暖如春稽坤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背糯俗。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工尿褪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人得湘。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓杖玲,卻偏偏與公主長得像,于是被迫代替她去往敵國和親淘正。 傳聞我的和親對象是個殘疾皇子摆马,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容

  • C#、Java鸿吆、python和go等語言中都有垃圾自動回收機制囤采,在對象失去引用的時候自動回收,而且基本上沒有指針的...
    StormZhu閱讀 3,738評論 1 15
  • 原作者:Babu_Abdulsalam 本文翻譯自CodeProject惩淳,轉(zhuǎn)載請注明出處蕉毯。 引入### Ooops...
    卡巴拉的樹閱讀 30,100評論 13 74
  • 導語: C++指針的內(nèi)存管理相信是大部分C++入門程序員的夢魘,受到Boost的啟發(fā)思犁,C++11標準推出了智能指針...
    7ee72f98ad17閱讀 895評論 0 1
  • C++裸指針的內(nèi)存問題有:1代虾、空懸指針/野指針2、重復釋放3激蹲、內(nèi)存泄漏4褐着、不配對的申請與釋放 使用智能指針可以有效...
    WalkeR_ZG閱讀 3,101評論 0 5
  • 蛋糕師培訓速成班 西安開店學蛋糕手藝先體驗后...
    西點烘焙培訓閱讀 130評論 0 0