灵魂拷问std::enable_shared_from_this,揭秘实现原理
引言
在C 编程中,使用智能指针是一种安全管理对象生命周期的方式。std::shared_ptr
是一种允许多个指针共享对象所有权的智能指针。然而,当一个对象需要获取对自身的shared_ptr
时,传统的方法可能导致未定义行为。为了解决这个问题,C 引入了std::enable_shared_from_this
类,本文将深入探讨其基础知识、使用案例以及内部实现。
首先抛出一些问题:
- enable_shared_from_this通常被用来做什么,什么场景被使用?
- enable_shared_from_this解决了什么问题?
- enable_shared_from_this的public、private继承为何需要特别注意,不然会引发什么问题?
- enable_shared_from_this内部的实现细节你知道多少呢?
std::shared_ptr基础知识
首先,我们回顾一下std::shared_ptr
的基础知识。它是一种智能指针,通过共享控制块的方式安全地管理对象的生命周期。多个 shared_ptr
实例通过共享的 控制块 结构来控制对象的生命周期。
当使用原始指针构造或初始化 shared_ptr
时,会创建一个新的控制块。为了确保对象仅由一个共享控制块管理,对对象的任何额外的 shared_ptr
实例必须通过复制已经存在的指向该对象的 shared_ptr
来产生,例如:
void run() {
auto p{new int(12)}; //p 是 int*
std::shared_ptr<int> sp1{p};
auto sp2{sp1}; //OK sp2 与 sp1 共享控制块
}
使用原始指针初始化已经由 shared_ptr
管理的对象会创建另一个控制块来管理该对象,这将导致未定义的行为。例如:
void bad_run() {
auto p{new int(12)};
std::shared_ptr<int> sp1{p};
std::shared_ptr<int> sp2{p}; //! 未定义行为
}
从一个原始指针实例化多个 shared_ptr
是一种严重后果的编程失误。在可能的情况下,尽量使用 std::make_shared
(或 std::allocate_shared
)来减少发生此错误的可能性。例如:
auto sp1 = std::make_shared<int>();
std::shared_ptr<int> sp2{sp1.get()}; // 这会发生什么?
然而,有些情况下,shared_ptr
托管的对象需要获得一个指向自己的 shared_ptr
。但首先,像下面这样尝试使用 this
指针创建 shared_ptr
不会起作用,原因如上所述:
struct Foo {
std::shared_ptr<Foo> getSelfPtr() {
return std::shared_ptr<Foo>(this);
}
//...
};
void run() {
auto sp1 = std::make_shared<Foo>();
auto sp2 = sp1->getSelfPtr(); //!! 未定义行为
/*sp1 和 sp2 有两个不同的控制块 管理相同的 Foo*/
}
这就是 std::enable_shared_from_this<T>
发挥作用的地方。公开继承 std::enable_shared_from_this
的类可以通过调用方法 shared_from_this()
获得指向自己的 shared_ptr
。以下是它的一个基本示例:
#include <memory>
struct Foo : std::enable_shared_from_this<Foo> {
std::shared_ptr<Foo> getSelfPtr() {
return shared_from_this();
}
//...
};
void run() {
auto sp1 = std::make_shared<Foo>();
auto sp2 = sp1->getSelfPtr(); // OK
/*sp1 和 sp2 共享相同的控制块,正确管理 Foo*/
}
enable_shared_from_this类初识
std::enable_shared_from_this 的实现是一个类,它只包含一个 weak_ptr 字段(通常称为 _M_weak_this
),这里面有很多细节:看看你知道吗?
_M_weak_this
成员是在何时被初始化的,怎么初始化的?- friend class声明在这里起到了什么作用?
template <typename _Tp>
class enable_shared_from_this
{
public:
shared_ptr<_Tp>
shared_from_this()
{ return shared_ptr<_Tp>(this->_M_weak_this); }
shared_ptr<const _Tp>
shared_from_this() const
{ return shared_ptr<const _Tp>(this->_M_weak_this); }
private:
template<typename _Tp1>
void
_M_weak_assign(_Tp1* __p, const __shared_count<>& __n) const noexcept
{ _M_weak_this._M_assign(__p, __n); }
// Found by ADL when this is an associated class.
friend const enable_shared_from_this*
__enable_shared_from_this_base(const __shared_count<>&,
const enable_shared_from_this* __p)
{ return __p; }
template<typename, _Lock_policy>
friend class __shared_ptr;
mutable weak_ptr<_Tp> _M_weak_this;
};
这里的friend声明特别重要,这样的话,__shared_ptr
便可以访问这个类的所有private成员,因此__shared_ptr
便可以访问_M_weak_assign
、__enable_shared_from_this_base
、_M_weak_this
。
至于_M_weak_this
在什么地方被初始化见下方内容。
实现原理
假设此时Foo继承了enable_shared_from_this,当我们编写这样一段代码到底放生了什么?
于此同时,我们要解决第一个问题:为何enable_shared_from_this需要public继承,私有继承会发生什么?
代码语言:javascript复制auto sp1 = std::make_shared<Foo>();
make_shared会调用allocate_shared,随后调用shared_ptr的构造函数,再调用__shared_ptr
的构造函数,此时我们可以看到会调用_M_enable_shared_from_this_with
,它是一个模版函数,此时会使用ADL从enable_shared_from_this类中查找enable_shared_from_this
。
template<typename _Alloc, typename... _Args>
__shared_ptr(_Sp_alloc_shared_tag<_Alloc> __tag, _Args&&... __args)
: _M_ptr(), _M_refcount(_M_ptr, __tag, std::forward<_Args>(__args)...)
{ _M_enable_shared_from_this_with(_M_ptr); }
查找到了则拿到base,通过访问私有函数_M_weak_assign
来初始化_M_weak_this
。如果没查找到,则不会初始化_M_weak_this
。
当我们通过public继承enable_shared_from_this时,可以正常的初始化_M_weak_this
,而如果是private继承,这里会走空实现,_M_weak_this
未被初始化。
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
{ }
make_shared
看起来一切正常,那为啥我还要强调上面这些逻辑呢,往下看。
当我们使用了shared_from_this()
,在private会报异常。
std::shared_ptr<Foo> getSelfPtr() { return shared_from_this(); }
shared_from_this()会基于已有的_M_weak_this
构造shared_ptr,_M_refcount
是一个__shared_count
对象。
template<typename _Yp, typename = _Compatible<_Yp>>
explicit __shared_ptr(const __weak_ptr<_Yp, _Lp>& __r)
: _M_refcount(__r._M_refcount) // may throw
{
// ...
}
这里会使用_M_weak_this
的_M_refcount
去初始化__shared_count
,而当私有继承时,_M_weak_this
并没有被初始化,于是引发了bad_weak_ptr
异常。
template<_Lock_policy _Lp>
inline
__shared_count<_Lp>::__shared_count(const __weak_count<_Lp>& __r)
: _M_pi(__r._M_pi)
{
if (_M_pi != nullptr)
_M_pi->_M_add_ref_lock();
else
__throw_bad_weak_ptr();
}