智能指针 与 shared_from_this 源码解析

注:本文转载原创作者“程序喵” 的《源码分析shared_ptr实现》。并对其结构进行了调整,内容做了补充。

背景

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

智能指针的种类

  • shared_ptr
  • weak_ptr
  • unique_ptr

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

shared_ptr的实现

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

非标准类图如下:
在这里插入图片描述
如上图,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的赋值构造函数:

// __shared_count 类

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()函数进而释放托管对象内存。

为何使用智能指针管理对象,基类析构函数不是虚函数也会调用子类析构函数?

多态的实现往往需要将基类的析构函数定义成虚函数数,即使用关键字virsual关键字,这是因为在父类指针指向子类对象Base * ptr = new Child();,进行delete操作delete ptr时候,只会调用基类的析构函数~Base(),不会调用子类析构函数,这样就有内存泄漏的风险。
使用智能指针:

std::shared_ptr<Base> pt(new Child());
......

在销毁ptr时会调用析构Child的析构函数,这是为什么呢?
原因是存储的指针原类型类是使用函数模板传入的类型Child,而不是类模板传入的Base。
代码如下:

template<typename _Tp>
class shared_ptr : public __shared_ptr<_Tp>{
    ......
    template<typename _Yp, typename = _Constructible<_Yp*>>
    explicit
    shared_ptr(_Yp* __p) : __shared_ptr<_Tp>(__p) { }
    ......
};

template <typename _Tp, _Lock_policy _Lp>
class __shared_ptr : public __shared_ptr_access<_Tp, _Lp> {
    ......
    template <typename _Yp, typename = _SafeConv<_Yp>>
    explicit __shared_ptr(_Yp* __p__p)
        : _M_ptr(__p), _M_refcount(__p, typename is_array<_Tp>::type()) {
        static_assert(!is_void<_Yp>::value, "incomplete type");
        static_assert(sizeof(_Yp) > 0, "incomplete type");
        _M_enable_shared_from_this_with(__p);
    }
    ......
}

可以看到,传入__shared_ptr构造函数的数据类型为Yp,并不是类的模板类型_Tp,所以在析构delete操作的时候实际上是对子类对象指针进行的delete。

weak_ptr

_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,我们之后的文章会讲到。

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。

对象中的_M_weak_this是何时初始化的呢?

在share_ptr构造时候,会检查是否继承了类enable_shared_from_this, 如果继承了,就将_M_weak_this初始化赋值。
如:

// class __shared_ptr

template <typename _Yp, typename = _SafeConv<_Yp>>
explicit __shared_ptr(_Yp* __p__p)
    : _M_ptr(__p), _M_refcount(__p, typename is_array<_Tp>::type()) {
    static_assert(!is_void<_Yp>::value, "incomplete type");
    static_assert(sizeof(_Yp) > 0, "incomplete type");
    _M_enable_shared_from_this_with(__p);   // 检测是否继承enable_shared_from_this,并复制
}

检测是否继承 enable_shared_from_this,并复制代码如下:

// class __shared_ptr

template <typename _Yp>
using __esft_base_t = decltype(__enable_shared_from_this_base(
    std::declval<const __shared_count<_Lp>&>(), std::declval<_Yp*>()));

// Detect an accessible and unambiguous enable_shared_from_this base.
template <typename _Yp, typename = void>
struct __has_esft_base : false_type {};

template <typename _Yp>
struct __has_esft_base<_Yp, __void_t<__esft_base_t<_Yp>>>
    : __not_<is_array<_Tp>> {
};  // No enable shared_from_this for arrays
//-------------------------1

template <typename _Yp, typename _Yp2 = typename remove_cv<_Yp>::type>
typename enable_if<__has_esft_base<_Yp2>::value>::type
_M_enable_shared_from_this_with(_Yp* __p) noexcept {
    if (auto __base = __enable_shared_from_this_base(_M_refcount, __p))
        __base->_M_weak_assign(const_cast<_Yp2*>(__p), _M_refcount);
}

template <typename _Yp, typename _Yp2 = typename remove_cv<_Yp>::type>
typename enable_if<!__has_esft_base<_Yp2>::value>::type
_M_enable_shared_from_this_with(_Yp*) noexcept {}

// __weak_ptr 类
class __weak_ptr {
    ......

    // Used by __enable_shared_from_this.
    void _M_assign(_Tp* __ptr,
                    const __shared_count<_Lp>& __refcount) noexcept {
        if (use_count() == 0) {
            _M_ptr = __ptr;
            _M_refcount = __refcount;
        }
    }
    ......
};

// class enable_shared_from_this

   friend const enable_shared_from_this*
      __enable_shared_from_this_base(const __shared_count<>&,
				     const enable_shared_from_this* __p)
      { return __p; }

上述的代码实现了检测与赋值的功能,这里使用了SFINAE的技术:如果匹配到继承(enable_shared_from_this)的模板,就进行赋值,如果匹配到无继承的模板,则什么也不做。

unique_ptr 如何实现

unique_ptr的实现较为简单,对象中只是存储了对象指针与删除器,对象析构时候调用delete,代码如下:

~unique_ptr() {
        auto& __ptr = _M_t._M_ptr();
        if (__ptr != nullptr) get_deleter()(__ptr);
        __ptr = pointer();
    }

如何实现不能拷贝与赋值的?

设置拷贝构造delete,重载赋值运算符为delete
代码如下:

// Disable copy from lvalue.
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;

总结

  • shared_ptr与weak_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的生命线。
  • unique_ptr只是设置了拷贝构造与重载赋值运算符为delete,来控制对象。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用enable_shared_from_this需要遵循以下步骤: 1. 定义一个类并继承自std::enable_shared_from_this<T> 2. 在类的构造函数中,将this指针传递给std::shared_ptr<T>对象。 3. 在需要使用shared_from_this()的地方,调用该函数,并将返回值赋值给一个std::shared_ptr<T>对象。 以下是一个示例代码: ``` #include <memory> #include <iostream> class MyClass : public std::enable_shared_from_this<MyClass> { public: MyClass(int val) : m_val(val) {} std::shared_ptr<MyClass> getShared() { return shared_from_this(); } int getValue() const { return m_val; } private: int m_val; }; int main() { std::shared_ptr<MyClass> ptr1(new MyClass(10)); std::shared_ptr<MyClass> ptr2 = ptr1->getShared(); std::cout << "ptr1 value: " << ptr1->getValue() << std::endl; std::cout << "ptr2 value: " << ptr2->getValue() << std::endl; return 0; } ``` 在这个例子中,我们定义了一个名为MyClass的类,这个类继承自std::enable_shared_from_this<MyClass>。在类的构造函数中,我们将this指针传递给std::shared_ptr<MyClass>对象。在getShared()函数中,我们调用shared_from_this()函数,并将返回值赋值给一个std::shared_ptr<MyClass>对象。 最后,我们创建了两个std::shared_ptr<MyClass>对象,一个是直接使用new操作符创建的,另一个是通过getShared()函数获取的。我们输出了这两个对象的值,可以看到它们都指向同一个对象,并且该对象的值为10。这说明了两个std::shared_ptr<MyClass>对象共享同一个对象。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值