C++:智能指针(5)——enable_shared_from_this工作原理、源码分析
文章目录
enable_shared_from_this的作用
在C++:智能指针(3)——无法通过原指针增加共享指针shared_ptr的计数,enable_shared_from_this解决中,我解释了智能指针share_ptr的缺陷:
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;
}
- 首先在堆上定义
Good * _OrigPtr=new Good();
- 然后分配一个
shared_ptr<Good> bp1(_OrigPtr);
- 最后最重要的,我们用
_OrigPtr->getptr();
获取了另一个shared_ptr<Good> bp2
我们看一下输出
bp1.use_count() = 2
bp2.use_count() = 2
Good::~Good() called
Program ended with exit code: 0
bp1
与bp2
共同增加了引用计数;;
问题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()
return shared_from_this();
{return shared_ptr<_Tp>(__weak_this_);}
报错的地方是shared_ptr(bp2构造调用)的构造函数
这部分内容比较复杂,请先阅读下面的源码分析与工作原理部分然后再回来
回到这里,相信你已经发现问题的根源:由于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
而报错;
:
问题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
而报错;
:
enable_shared_from_this源码分析、工作原理
这部分涉及了CRTP(curiously recurring template pattern),主要是解决多态效率底下的问题,虽然在enable_shared_from_this这一部分我没有看到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_)
报错;
这警告我们
:
: