源碼分析shared_ptr實現(xiàn)

智能指針是C++中一項很常用的技術(shù)怔接,合理的使用智能指針可以更方便的管理內(nèi)存搪泳,降低內(nèi)存泄漏的風(fēng)險,這里只介紹C++11后官方的智能指針扼脐。

智能指針的種類

  • unique_ptr
  • shared_ptr
  • weak_ptr
    關(guān)于智能指針使用以及區(qū)別可以自行查找資料岸军,這里主要介紹智能指針的實現(xiàn)原理

shared_ptr的實現(xiàn)

我們平時看文檔都知道shared_ptr內(nèi)部是使用引用計數(shù)來記錄托管指針被引用的次數(shù),當(dāng)托管指針的引用計數(shù)為0時會釋放托管的內(nèi)存瓦侮,這里通過gcc源碼探究shared_ptr內(nèi)部究竟是如何實現(xiàn)的內(nèi)存引用計數(shù)功能艰赞。

非標(biāo)準(zhǔn)類圖如下:

shared_ptr.jpg

如圖,shared_ptr類幾乎什么都沒有做脏榆,它是繼承了__shared_ptr, __shared_ptr內(nèi)部有一個類型為__shared_count類型的成員_M_refcount, __shared_count內(nèi)部有類型為_Sp_counted_base*的_M_pi的成員猖毫, _Sp_counted_base才是整個shared_ptr功能的核心,通過_Sp_counted_base控制引用計數(shù)來管理托管的內(nèi)存须喂,由圖可見_Sp_counted_base內(nèi)部不持有托管內(nèi)存的指針吁断,這里__shared_count內(nèi)部的成員其實是一個繼承自_Sp_counted_base的_Sp_counted_ptr類型,_Sp_counted_ptr類型內(nèi)部持有托管內(nèi)存的指針_M_ptr, _M_pi是一個_Sp_counted_base基類對象指針坞生,指向_Sp_counted_ptr子類對象內(nèi)存仔役,這樣_M_pi內(nèi)部就既可以控制引用計數(shù),又可以在最后釋放托管內(nèi)存是己。

這里稱_M_pi為管理對象又兵,它內(nèi)部的_M_ptr為托管對象,管理同一塊托管對象的多個shared_ptr內(nèi)部共用一個管理對象(_M_pi), 這里的多個shared_ptr可能是通過第一個shared_ptr拷貝或者移動而來, 管理對象內(nèi)部有兩個成員變量_M_use_count和_M_weak_count, _M_use_count表示托管對象的引用計數(shù),控制托管對象什么時候析構(gòu)和釋放沛厨,大概就是有N個shared_ptr的拷貝那引用計數(shù)就是N宙地,當(dāng)引用計數(shù)為0時調(diào)用托管對象的析構(gòu)函數(shù)且釋放內(nèi)存。_M_weak_count表示管理對象的引用計數(shù)逆皮,管理對象也是一個內(nèi)存指針宅粥,這塊指針是初始化第一個shared_ptr時new出來的,到最后也需要delete电谣,所以使用_M_weak_count來控制管理對象什么時候析構(gòu)秽梅,我們平時用到的weak_ptr內(nèi)部其實持有的就是這個管理對象的指針,當(dāng)weak_ptr拷貝時剿牺,管理對象的引用計數(shù)_M_weak_count就會增加企垦,當(dāng)_M_weak_count為0時,管理對象_M_pi就會析構(gòu)且釋放內(nèi)存晒来。

_M_use_count是如何加減的

_M_use_count表示托管對象的引用計數(shù)钞诡,即當(dāng)shared_ptr拷貝時會增加,當(dāng)shared_ptr析構(gòu)時會減少潜索,看精簡代碼:

template <typename _Yp>
__shared_ptr(const __shared_ptr<_Yp, _Lp>& __r,
                element_type* __p) noexcept
    : _M_ptr(__p), _M_refcount(__r._M_refcount)  // never throws
{
}

__shared_count(const __shared_count& __r) noexcept : _M_pi(__r._M_pi)
{
    if (_M_pi != 0) _M_pi->_M_add_ref_copy();
}

template <>
inline void _Sp_counted_base<_S_single>::_M_add_ref_copy()
{
    ++_M_use_count;
}

shared_ptr拷貝時臭增,內(nèi)部__shared_count類型的_M_refcount會進行拷貝,__shared_count的拷貝構(gòu)造函數(shù)會調(diào)用_M_add_ref_copy()方法竹习,_M_add_ref_copy()方法中會將_M_use_count加1。

這里再看下shared_ptr的賦值構(gòu)造函數(shù):

template <typename _Yp>
_Assignable<const shared_ptr<_Yp>&> operator=(
    const shared_ptr<_Yp>& __r) noexcept
{
    this->__shared_ptr<_Tp>::operator=(__r);
    return *this;
}

template <typename _Yp>
_Assignable<_Yp> operator=(const __shared_ptr<_Yp, _Lp>& __r) noexcept
{
    _M_ptr = __r._M_ptr;
    _M_refcount = __r._M_refcount;  // __shared_count::op= doesn't throw
    return *this;
}

__shared_count& operator=(const __shared_count& __r) noexcept
{
    _Sp_counted_base<_Lp>* __tmp = __r._M_pi;
    if (__tmp != _M_pi) {
        if (__tmp != 0) __tmp->_M_add_ref_copy();
        if (_M_pi != 0) _M_pi->_M_release();
        _M_pi = __tmp;
    }
    return *this;
}

從代碼中可見列牺,shared_ptr的operator=會調(diào)用__shared_ptr的operator=進而調(diào)用__shared_count的operator=整陌,從這里可以看出管理同一塊托管對象的shared_ptr共用的同一個管理對象的指針。

_M_use_count是如何減為0的瞎领,可以猜想到shared_ptr析構(gòu)時會調(diào)用__shared_count的析構(gòu)函數(shù)泌辫,看精簡代碼:

~__shared_count() noexcept
{
    if (_M_pi != nullptr) _M_pi->_M_release();
}

template <>
inline void _Sp_counted_base<_S_single>::_M_release() noexcept
{
    if (--_M_use_count == 0) {
        _M_dispose();
        if (--_M_weak_count == 0) _M_destroy();
    }
}

virtual void _M_dispose() noexcept { delete _M_ptr; }

在shared_ptr生命周期結(jié)束析構(gòu)時會將引用計數(shù)減1,如果引用引用計數(shù)為0九默,會調(diào)用_M_dispose()函數(shù)進而釋放托管對象內(nèi)存震放。

_M_weak_count是如何加減的

上面的代碼中可以看見--_M_weak_count為0時,會調(diào)用_M_destroy()函數(shù)驼修,這里看看--_M_weak_count是如何加減的殿遂。

管理對象初始化時_M_weak_count的初始值為1

_Sp_counted_base() noexcept : _M_use_count(1), _M_weak_count(1) {}

注意當(dāng)shared_ptr拷貝或者移動時_M_weak_count是不會增加的,它表示的是管理對象的計數(shù)乙各,只有當(dāng)__M_use_count為0時_M_weak_count才會減1墨礁,除此之外_M_weak_count的數(shù)值是由weak_ptr控制的。

由上面類圖可以看見weak_ptr內(nèi)部其實和shared_ptr內(nèi)部持有的是同一個管理對象指針耳峦,即_Sp_counted_base的指針恩静,當(dāng)weak_ptr拷貝析構(gòu)時候,_Sp_counted_base內(nèi)部的_M_weak_count會相應(yīng)加減蹲坷。

__weak_count(const __weak_count& __r) noexcept : _M_pi(__r._M_pi)
{
    if (_M_pi != nullptr) _M_pi->_M_weak_add_ref();
}

template <>
inline void _Sp_counted_base<_S_single>::_M_weak_add_ref() noexcept
{
    ++_M_weak_count;
}

~__weak_count() noexcept
{
    if (_M_pi != nullptr) _M_pi->_M_weak_release();
}

template <>
inline void _Sp_counted_base<_S_single>::_M_weak_release() noexcept
{
    if (--_M_weak_count == 0) _M_destroy();
}

virtual void _M_destroy() noexcept { delete this; }

從代碼中可以看出驶乾,weak_ptr拷貝時_M_weak_count加1邑飒,析構(gòu)時_M_weak_count減1,當(dāng)_M_weak_count為0時级乐,表示不再需要管理對象來控制托管對象疙咸,調(diào)用_M_destroy()的delete this來釋放管理對象內(nèi)存。
關(guān)于delete this可以看這篇文章http://www.reibang.com/p/c0235cd39fa2

weak_ptr的expired()和lock()做了什么

bool expired() const noexcept
{
    return _M_refcount._M_get_use_count() == 0;
}

weak_ptr的expired()函數(shù)只是看了托管對象的引用計數(shù)是否為0唇牧,為0返回true

__shared_ptr<_Tp, _Lp> lock() const noexcept
{
    return __shared_ptr<element_type, _Lp>(*this, std::nothrow);
}

__shared_ptr(const __weak_ptr<_Tp, _Lp>& __r, std::nothrow_t)
    : _M_refcount(__r._M_refcount, std::nothrow)
{
    _M_ptr = _M_refcount._M_get_use_count() ? __r._M_ptr : nullptr;
}

weak_ptr的lock()函數(shù)是打算返回一個shared_ptr對象來延長托管對象的生命周期罕扎,這里返回后需要判斷返回值是否為nullptr。

shared_from_this()是如何實現(xiàn)的

精簡代碼如下:

class enable_shared_from_this
{
    shared_ptr<const _Tp> shared_from_this() const
    {
        return shared_ptr<const _Tp>(this->_M_weak_this);
    }

    mutable weak_ptr<_Tp> _M_weak_this;
};

使用shared_from_this()的類需要繼承enable_shared_from_this類丐重,enable_shared_from_this類中持有一個類型為weak_ptr的成員_M_weak_this腔召,調(diào)用shared_from_this()就是將內(nèi)部持有的weak_ptr轉(zhuǎn)成了shared_ptr。

總結(jié)

shared_ptr內(nèi)部使用__shared_count中的_Sp_counted_base對象來控制托管指針扮惦,_Sp_counted_base內(nèi)部有_M_use_count和_M_weak_count臀蛛,_M_use_count表示托管指針的引用計數(shù),_M_weak_count表示_Sp_counted_base的引用計數(shù)崖蜜,_M_use_count為0時候釋放托管指針指向的內(nèi)存浊仆,_M_weak_count為0時釋放_Sp_counted_base指向的內(nèi)存,這里_Sp_counted_base的生命線一般不會短于shared_ptr的生命線豫领。

注意:文中所說的釋放指針指向的內(nèi)存不太準(zhǔn)確抡柿,這里表示delete *ptr,既調(diào)用析構(gòu)函數(shù)又釋放相應(yīng)內(nèi)存等恐。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末洲劣,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子课蔬,更是在濱河造成了極大的恐慌囱稽,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件二跋,死亡現(xiàn)場離奇詭異战惊,居然都是意外死亡,警方通過查閱死者的電腦和手機扎即,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門吞获,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人铺遂,你說我怎么就攤上這事衫哥。” “怎么了襟锐?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵撤逢,是天一觀的道長。 經(jīng)常有香客問我,道長蚊荣,這世上最難降的妖魔是什么初狰? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮互例,結(jié)果婚禮上奢入,老公的妹妹穿的比我還像新娘。我一直安慰自己媳叨,他們只是感情好腥光,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著糊秆,像睡著了一般武福。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上痘番,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天捉片,我揣著相機與錄音,去河邊找鬼汞舱。 笑死伍纫,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的昂芜。 我是一名探鬼主播莹规,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼泌神!你這毒婦竟也來了访惜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤腻扇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后砾嫉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體幼苛,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年焕刮,在試婚紗的時候發(fā)現(xiàn)自己被綠了舶沿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡配并,死狀恐怖括荡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情溉旋,我是刑警寧澤畸冲,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響邑闲,放射性物質(zhì)發(fā)生泄漏算行。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一苫耸、第九天 我趴在偏房一處隱蔽的房頂上張望州邢。 院中可真熱鬧,春花似錦褪子、人聲如沸量淌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽呀枢。三九已至,卻和暖如春渔扎,著一層夾襖步出監(jiān)牢的瞬間硫狞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工晃痴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留残吩,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓倘核,卻偏偏與公主長得像泣侮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子紧唱,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

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

  • 導(dǎo)語: C++指針的內(nèi)存管理相信是大部分C++入門程序員的夢魘活尊,受到Boost的啟發(fā),C++11標(biāo)準(zhǔn)推出了智能指針...
    7ee72f98ad17閱讀 889評論 0 1
  • shared_ptr 類 類似 vector漏益,智能指針也是模板蛹锰。因此,當(dāng)我們創(chuàng)建一個智能指針時绰疤,必須提供額外的信息...
    趙者也閱讀 848評論 0 0
  • C++裸指針的內(nèi)存問題有:1铜犬、空懸指針/野指針2、重復(fù)釋放3轻庆、內(nèi)存泄漏4癣猾、不配對的申請與釋放 使用智能指針可以有效...
    WalkeR_ZG閱讀 3,098評論 0 5
  • 12.1 智能指針 智能指針行為類似普通指針,但它負(fù)責(zé)自動釋放所知的對象余爆。 #include <memory> s...
    龍遁流閱讀 362評論 0 1
  • C#纷宇、Java、python和go等語言中都有垃圾自動回收機制蛾方,在對象失去引用的時候自動回收像捶,而且基本上沒有指針的...
    StormZhu閱讀 3,734評論 1 15