欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C++:智能指针(5)——enable_shared_from_this工作原理、源码分析

程序员文章站 2024-03-14 10:01:34
...

enable_shared_from_this的作用

C++:智能指针(3)——无法通过原指针增加共享指针shared_ptr的计数,enable_shared_from_this解决中,我解释了智能指针share_ptr的缺陷:\color{red}无法通过原指针增加共享指针的统一引用计数

    int * pt = new int();
    shared_ptr<int> spt1(pt);
    shared_ptr<int> spt2(pt);
    std::cout << "spt1.use_count() = " << spt1.use_count() << std::endl;
    std::cout << "spt2.use_count() = " << spt2.use_count() << std::endl;

导致重复释放

spt1.use_count() = 1
spt2.use_count() = 1
LeetCodePlayground(6326,0x1000dbdc0) malloc: *** error for object 0x102b5b8d0: pointer being freed was not allocated
LeetCodePlayground(6326,0x1000dbdc0) malloc: *** set a breakpoint in malloc_error_break to debug
(lldb) 

这个问题在操作类实例时,使得我们无法从this直接构造shared_ptr;(enable_shared_from_this的本质是在为类对象与引用计数不存放在一起这件事擦屁股)


用法

class Good : public std::enable_shared_from_this<Good> // 必须公有继承
{
public:
    std::shared_ptr<Good> getptr() {
        return shared_from_this();
    }
    ~Good() { std::cout << "Good::~Good() called" << std::endl; }
};

shared_from_this();来自std::enable_shared_from_this稍后讲解

int main()
{
    Good * _OrigPtr=new Good();
    std::shared_ptr<Good> bp1(_OrigPtr);
    std::shared_ptr<Good> bp2 = _OrigPtr->getptr();
    // 打印bp1和bp2的引用计数
    std::cout << "bp1.use_count() = " << bp1.use_count() << std::endl;
    std::cout << "bp2.use_count() = " << bp2.use_count() << std::endl;
    return 0;
}
  1. 首先在堆上定义Good * _OrigPtr=new Good();
  2. 然后分配一个shared_ptr<Good> bp1(_OrigPtr);
  3. 最后最重要的,我们用_OrigPtr->getptr();获取了另一个shared_ptr<Good> bp2

我们看一下输出

bp1.use_count() = 2
bp2.use_count() = 2
Good::~Good() called
Program ended with exit code: 0

bp1bp2共同增加了引用计数;\color{red}解决了无法通过原指针增加共享指针的引用计数


问题1分析

一切正常
直到我不小心注释掉了一行代码:

  1. 然后分配一个shared_ptr<Good> bp1(_OrigPtr);
int main()
{
    Good * _OrigPtr=new Good();
//    std::shared_ptr<Good> bp1(_OrigPtr);
    std::shared_ptr<Good> bp2 = _OrigPtr->getptr();//这行run time error!!!!
    // 打印bp1和bp2的引用计数
//    std::cout << "bp1.use_count() = " << bp1.use_count() << std::endl;
    std::cout << "bp2.use_count() = " << bp2.use_count() << std::endl;
    return 0;
}

libc++abi.dylib: terminating with uncaught exception of type std::__1::bad_weak_ptr: bad_weak_ptr
(lldb)

奇了怪了,为什么这里注释掉了shared_ptr<Good> bp1,结果却导致了_OrigPtr->getptr();无法获取新的shared_ptr了呢?

抛出错误的调用栈如下:std::shared_ptr<Good> bp2 = _OrigPtr->getptr()
C++:智能指针(5)——enable_shared_from_this工作原理、源码分析
return shared_from_this();C++:智能指针(5)——enable_shared_from_this工作原理、源码分析
{return shared_ptr<_Tp>(__weak_this_);}C++:智能指针(5)——enable_shared_from_this工作原理、源码分析
报错的地方是shared_ptr(bp2构造调用)的构造函数C++:智能指针(5)——enable_shared_from_this工作原理、源码分析
这部分内容比较复杂,请先阅读下面的源码分析与工作原理部分然后再回来

回到这里,相信你已经发现问题的根源:由于enable_shared_from_this与shared_ptr的耦合关系, __weak_this_变量的初始化是通过__enable_weak_this(__p, __p),在shared_ptr的初始化时完成的,

当注释掉了shared_ptr<Good> bp1,导致__weak_this_=nullptr,所以

    shared_ptr<_Tp> shared_from_this()
        {return shared_ptr<_Tp>(__weak_this_);}

->

template<class _Tp>
template<class _Yp>
shared_ptr<_Tp>::shared_ptr(const weak_ptr<_Yp>& __r,
                            typename enable_if<is_convertible<_Yp*, element_type*>::value, __nat>::type)
    : __ptr_(__r.__ptr_),
      __cntrl_(__r.__cntrl_ ? __r.__cntrl_->lock() : __r.__cntrl_)
{
    if (__cntrl_ == 0)
        __throw_bad_weak_ptr();
}

会因为__r.__cntrl_==0而报错;

this+enable_shared_from_thisshared_ptr\color{red}在利用this+enable\_shared\_from\_this构造shared\_ptr时
shared_ptr\color{red}一定要保证已经存在指向该实例的shared\_ptr


问题2分析

以封装为基本思想的C++必然支持private继承吧?

class Good : private(protected) std::enable_shared_from_this<Good> // 必须公有继承
{
public:
    std::shared_ptr<Good> getptr() {
        return shared_from_this();
    }
    ~Good() { std::cout << "Good::~Good() called" << std::endl; }
};

libc++abi.dylib: terminating with uncaught exception of type std::__1::bad_weak_ptr: bad_weak_ptr
(lldb)

又报错了??这是为什么呢?
这部分内容比较复杂,请先阅读下面的源码分析与工作原理部分然后再回来

回到这里,在我们的例子里,根据C++:继承(2)——public继承的赋值兼容规则(基类指针指向子类对象),在public继承时向下转换当然是安全的,于是Good*可以转换为enable_shared_from_this<Good>*于是调用了非平凡版本的__enable_weak_this(__p, __p)
但是在private或protected继承中,向下转换不是安全的,这说明在初始化shared_ptr<Good> bp1__enable_weak_this(__p, __p)调用的是优先级最低的平凡版本void __enable_weak_this(...) _NOEXCEPT {}

那么 __weak_this_=nullptr,所以

    shared_ptr<_Tp> shared_from_this()
        {return shared_ptr<_Tp>(__weak_this_);}

->

template<class _Tp>
template<class _Yp>
shared_ptr<_Tp>::shared_ptr(const weak_ptr<_Yp>& __r,
                            typename enable_if<is_convertible<_Yp*, element_type*>::value, __nat>::type)
    : __ptr_(__r.__ptr_),
      __cntrl_(__r.__cntrl_ ? __r.__cntrl_->lock() : __r.__cntrl_)
{
    if (__cntrl_ == 0)
        __throw_bad_weak_ptr();
}

会因为__r.__cntrl_==0而报错;

this+enable_shared_from_thisshared_ptr\color{red}在利用this+enable\_shared\_from\_this构造shared\_ptr时
public\color{red}一定要保证public继承


enable_shared_from_this源码分析、工作原理

这部分涉及了CRTP(curiously recurring template pattern),主要是解决多态效率底下的问题,虽然在enable_shared_from_this这一部分我没有看到CRTP的具体作用,在这里提一下是为了避免疑惑;

C++编程模式CRTP
C++ 惯用法 CRTP 简介

template<class _Tp>
class _LIBCPP_TEMPLATE_VIS enable_shared_from_this
{
    mutable weak_ptr<_Tp> __weak_this_;
protected:
    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR
    enable_shared_from_this() _NOEXCEPT {}
    _LIBCPP_INLINE_VISIBILITY
    enable_shared_from_this(enable_shared_from_this const&) _NOEXCEPT {}
    _LIBCPP_INLINE_VISIBILITY
    enable_shared_from_this& operator=(enable_shared_from_this const&) _NOEXCEPT
        {return *this;}
    _LIBCPP_INLINE_VISIBILITY
    ~enable_shared_from_this() {}
public:
    _LIBCPP_INLINE_VISIBILITY
    shared_ptr<_Tp> shared_from_this()
        {return shared_ptr<_Tp>(__weak_this_);}
    _LIBCPP_INLINE_VISIBILITY
    shared_ptr<_Tp const> shared_from_this() const
        {return shared_ptr<const _Tp>(__weak_this_);}

#if _LIBCPP_STD_VER > 14
    _LIBCPP_INLINE_VISIBILITY
    weak_ptr<_Tp> weak_from_this() _NOEXCEPT
       { return __weak_this_; }

    _LIBCPP_INLINE_VISIBILITY
    weak_ptr<const _Tp> weak_from_this() const _NOEXCEPT
        { return __weak_this_; }
#endif // _LIBCPP_STD_VER > 14

    template <class _Up> friend class shared_ptr;
};

看完这个源码,不知道你是不是跟我有同样的疑惑:唯一一个private变量mutable weak_ptr<_Tp> __weak_this_;居然没有构造函数进行初始化赋值;


在这里我先给出解答:
enable_shared_from_this和shared_ptr是有耦合关系的(从友元类的定义就可以看到template <class _Up> friend class shared_ptr;)。
在对类实例定义shared_ptr时 std::shared_ptr<Good> bp1(_OrigPtr);完成的工作不仅是完成bp1的初始化;
最重要的是,当继承如下class Good : public std::enable_shared_from_this<Good>其实是告诉编译器is_convertible<Good*, const enable_shared_from_this<Good>* >::value=true,那么根据SFINAE规则,声明Good类的shared_ptr时会调用__enable_weak_this对此private变量mutable weak_ptr<_Tp> __weak_this_;进行初始化或赋值;


enable_shared_from_this与shared_ptr的耦合关系

根据SFINAE规则,一般的通过原指针定义shared_ptr的构造函数如下:

template<class _Tp>
template<class _Yp>
shared_ptr<_Tp>::shared_ptr(_Yp* __p,
                            typename enable_if<is_convertible<_Yp*, element_type*>::value, __nat>::type)
    : __ptr_(__p)
{
    unique_ptr<_Yp> __hold(__p);
    typedef typename __shared_ptr_default_allocator<_Yp>::type _AllocT;
    typedef __shared_ptr_pointer<_Yp*, default_delete<_Yp>, _AllocT > _CntrlBlk;
    __cntrl_ = new _CntrlBlk(__p, default_delete<_Yp>(), _AllocT());
    __hold.release();
    __enable_weak_this(__p, __p);
}

在我们例子里_TP=element_type=_Yp=Good,所以调用了如上的构造函数(如果采用不同重载构造函数进行构造,只要保证非_Yp* =nullptr构造,如下讨论均成立)

关注这样一行语句:__enable_weak_this(__p, __p);,首先看一下这个函数的2个定义

    template <class _Yp, class _OrigPtr>
        _LIBCPP_INLINE_VISIBILITY
        typename enable_if<is_convertible<_OrigPtr*,
                                          const enable_shared_from_this<_Yp>*
        >::value,
            void>::type
        __enable_weak_this(const enable_shared_from_this<_Yp>* __e,
                           _OrigPtr* __ptr) _NOEXCEPT
        {
            typedef typename remove_cv<_Yp>::type _RawYp;
            if (__e && __e->__weak_this_.expired())
            {
                __e->__weak_this_ = shared_ptr<_RawYp>(*this,
                    const_cast<_RawYp*>(static_cast<const _Yp*>(__ptr)));
            }
        }
        
	_LIBCPP_INLINE_VISIBILITY void __enable_weak_this(...) _NOEXCEPT {}

根据C++:模版(1)——:SFINAE和std::enable_if
我们知道这个函数在_OrigPtr*可以转换为enable_shared_from_this<_Yp>*的情况下有效。
而在转换无法进行时,会调用优先级最低的可变参数版本void __enable_weak_this(...) _NOEXCEPT {}

在我们的例子里,根据C++:继承(2)——public继承的赋值兼容规则(基类指针指向子类对象),在public继承时向下转换当然是安全的,于是Good*可以转换为enable_shared_from_this<Good>*

于是__enable_weak_this(__p, __p);

          if (__e && __e->__weak_this_.expired())
            {
                __e->__weak_this_ = shared_ptr<_RawYp>(*this,
                    const_cast<_RawYp*>(static_cast<const _Yp*>(__ptr)));
            }

即可完成对enable_shared_from_this的private变量mutable weak_ptr<_Tp> __weak_this_;的初始化;


总结

enable_shared_from_this与shared_ptr的耦合关系比较特殊(友元类)。
能继承enable_shared_from_this<_TP>、利用其+this获得shared_ptr的关键在于其类内的private变量mutable weak_ptr<_Tp> __weak_this_;,而这个变量初始化必须要在shared_ptr<_TP>初始化时完成;

如果不初始化一个指向该地址的shared_ptr<_TP>指针,或者protected/private继承;则会导致__weak_this_=nullptr,然后导致shared_ptr<_Tp>(__weak_this_)报错;

这警告我们
this+enable_shared_from_thisshared_ptr\color{red}在利用this+enable\_shared\_from\_this构造shared\_ptr时
shared_ptr\color{red}一定要保证已经存在指向该实例的shared\_ptr

this+enable_shared_from_thisshared_ptr\color{red}在利用this+enable\_shared\_from\_this构造shared\_ptr时
public\color{red}一定要保证public继承

相关标签: c++