源码分析shared_ptr实现

智能指针是C++中一项很常用的技术,合理的使用智能指针可以更方便的管理内存,降低内存泄漏的风险,这里只介绍C++11后官方的智能指针。

智能指针的种类

  • unique_ptr
  • shared_ptr
  • weak_ptr
    关于智能指针使用以及区别可以自行查找资料,这里主要介绍智能指针的实现原理

shared_ptr的实现

我们平时看文档都知道shared_ptr内部是使用引用计数来记录托管指针被引用的次数,当托管指针的引用计数为0时会释放托管的内存,这里通过gcc源码探究shared_ptr内部究竟是如何实现的内存引用计数功能。

非标准类图如下:

shared_ptr.jpg

如图,shared_ptr类几乎什么都没有做,它是继承了__shared_ptr, __shared_ptr内部有一个类型为__shared_count类型的成员_M_refcount, __shared_count内部有类型为_Sp_counted_base*的_M_pi的成员, _Sp_counted_base才是整个shared_ptr功能的核心,通过_Sp_counted_base控制引用计数来管理托管的内存,由图可见_Sp_counted_base内部不持有托管内存的指针,这里__shared_count内部的成员其实是一个继承自_Sp_counted_base的_Sp_counted_ptr类型,_Sp_counted_ptr类型内部持有托管内存的指针_M_ptr, _M_pi是一个_Sp_counted_base基类对象指针,指向_Sp_counted_ptr子类对象内存,这样_M_pi内部就既可以控制引用计数,又可以在最后释放托管内存。

这里称_M_pi为管理对象,它内部的_M_ptr为托管对象,管理同一块托管对象的多个shared_ptr内部共用一个管理对象(_M_pi), 这里的多个shared_ptr可能是通过第一个shared_ptr拷贝或者移动而来, 管理对象内部有两个成员变量_M_use_count和_M_weak_count, _M_use_count表示托管对象的引用计数,控制托管对象什么时候析构和释放,大概就是有N个shared_ptr的拷贝那引用计数就是N,当引用计数为0时调用托管对象的析构函数且释放内存。_M_weak_count表示管理对象的引用计数,管理对象也是一个内存指针,这块指针是初始化第一个shared_ptr时new出来的,到最后也需要delete,所以使用_M_weak_count来控制管理对象什么时候析构,我们平时用到的weak_ptr内部其实持有的就是这个管理对象的指针,当weak_ptr拷贝时,管理对象的引用计数_M_weak_count就会增加,当_M_weak_count为0时,管理对象_M_pi就会析构且释放内存。

_M_use_count是如何加减的

_M_use_count表示托管对象的引用计数,即当shared_ptr拷贝时会增加,当shared_ptr析构时会减少,看精简代码:

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拷贝时,内部__shared_count类型的_M_refcount会进行拷贝,__shared_count的拷贝构造函数会调用_M_add_ref_copy()方法,_M_add_ref_copy()方法中会将_M_use_count加1。

这里再看下shared_ptr的赋值构造函数:

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=会调用__shared_ptr的operator=进而调用__shared_count的operator=,从这里可以看出管理同一块托管对象的shared_ptr共用的同一个管理对象的指针。

_M_use_count是如何减为0的,可以猜想到shared_ptr析构时会调用__shared_count的析构函数,看精简代码:

~__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生命周期结束析构时会将引用计数减1,如果引用引用计数为0,会调用_M_dispose()函数进而释放托管对象内存。

_M_weak_count是如何加减的

上面的代码中可以看见--_M_weak_count为0时,会调用_M_destroy()函数,这里看看--_M_weak_count是如何加减的。

管理对象初始化时_M_weak_count的初始值为1

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

注意当shared_ptr拷贝或者移动时_M_weak_count是不会增加的,它表示的是管理对象的计数,只有当__M_use_count为0时_M_weak_count才会减1,除此之外_M_weak_count的数值是由weak_ptr控制的。

由上面类图可以看见weak_ptr内部其实和shared_ptr内部持有的是同一个管理对象指针,即_Sp_counted_base的指针,当weak_ptr拷贝析构时候,_Sp_counted_base内部的_M_weak_count会相应加减。

__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,析构时_M_weak_count减1,当_M_weak_count为0时,表示不再需要管理对象来控制托管对象,调用_M_destroy()的delete this来释放管理对象内存。
关于delete this可以看这篇文章https://www.jianshu.com/p/c0235cd39fa2

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

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

weak_ptr的expired()函数只是看了托管对象的引用计数是否为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()函数是打算返回一个shared_ptr对象来延长托管对象的生命周期,这里返回后需要判断返回值是否为nullptr。

shared_from_this()是如何实现的

精简代码如下:

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,调用shared_from_this()就是将内部持有的weak_ptr转成了shared_ptr。

总结

shared_ptr内部使用__shared_count中的_Sp_counted_base对象来控制托管指针,_Sp_counted_base内部有_M_use_count和_M_weak_count,_M_use_count表示托管指针的引用计数,_M_weak_count表示_Sp_counted_base的引用计数,_M_use_count为0时候释放托管指针指向的内存,_M_weak_count为0时释放_Sp_counted_base指向的内存,这里_Sp_counted_base的生命线一般不会短于shared_ptr的生命线。

注意:文中所说的释放指针指向的内存不太准确,这里表示delete *ptr,既调用析构函数又释放相应内存。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容

  • 导语: C++指针的内存管理相信是大部分C++入门程序员的梦魇,受到Boost的启发,C++11标准推出了智能指针...
    7ee72f98ad17阅读 818评论 0 1
  • shared_ptr 类 类似 vector,智能指针也是模板。因此,当我们创建一个智能指针时,必须提供额外的信息...
    赵者也阅读 808评论 0 0
  • C++裸指针的内存问题有:1、空悬指针/野指针2、重复释放3、内存泄漏4、不配对的申请与释放 使用智能指针可以有效...
    WalkeR_ZG阅读 3,001评论 0 5
  • 12.1 智能指针 智能指针行为类似普通指针,但它负责自动释放所知的对象。 #include <memory> s...
    龙遁流阅读 331评论 0 1
  • C#、Java、python和go等语言中都有垃圾自动回收机制,在对象失去引用的时候自动回收,而且基本上没有指针的...
    StormZhu阅读 3,612评论 1 15