智能指針是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類幾乎什么都沒有做脏榆,它是繼承了__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)存等恐。