灵魂拷问std::enable_shared_from_this,揭秘实现原理

2023-11-22 17:12:32 浏览数 (2)

灵魂拷问std::enable_shared_from_this,揭秘实现原理

引言

在C 编程中,使用智能指针是一种安全管理对象生命周期的方式。std::shared_ptr是一种允许多个指针共享对象所有权的智能指针。然而,当一个对象需要获取对自身的shared_ptr时,传统的方法可能导致未定义行为。为了解决这个问题,C 引入了std::enable_shared_from_this类,本文将深入探讨其基础知识、使用案例以及内部实现。

首先抛出一些问题:

  1. enable_shared_from_this通常被用来做什么,什么场景被使用?
  2. enable_shared_from_this解决了什么问题?
  3. enable_shared_from_this的public、private继承为何需要特别注意,不然会引发什么问题?
  4. enable_shared_from_this内部的实现细节你知道多少呢?

std::shared_ptr基础知识

首先,我们回顾一下std::shared_ptr的基础知识。它是一种智能指针,通过共享控制块的方式安全地管理对象的生命周期。多个 shared_ptr 实例通过共享的 控制块 结构来控制对象的生命周期。

当使用原始指针构造或初始化 shared_ptr 时,会创建一个新的控制块。为了确保对象仅由一个共享控制块管理,对对象的任何额外的 shared_ptr 实例必须通过复制已经存在的指向该对象的 shared_ptr 来产生,例如:

代码语言:javascript复制
void run() {
 auto p{new int(12)}; //p 是 int*
 std::shared_ptr<int> sp1{p}; 
 auto sp2{sp1}; //OK sp2 与 sp1 共享控制块
}

使用原始指针初始化已经由 shared_ptr 管理的对象会创建另一个控制块来管理该对象,这将导致未定义的行为。例如:

代码语言:javascript复制
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)来减少发生此错误的可能性。例如:

代码语言:javascript复制
auto sp1 = std::make_shared<int>();
std::shared_ptr<int> sp2{sp1.get()}; // 这会发生什么?

然而,有些情况下,shared_ptr 托管的对象需要获得一个指向自己的 shared_ptr。但首先,像下面这样尝试使用 this 指针创建 shared_ptr 不会起作用,原因如上所述:

代码语言:javascript复制
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。以下是它的一个基本示例:

代码语言:javascript复制
#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声明在这里起到了什么作用?
代码语言:javascript复制
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

代码语言:javascript复制
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未被初始化。

代码语言:javascript复制
    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会报异常。

代码语言:javascript复制
std::shared_ptr<Foo> getSelfPtr() { return shared_from_this(); }

shared_from_this()会基于已有的_M_weak_this构造shared_ptr,_M_refcount是一个__shared_count对象。

代码语言:javascript复制
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异常。

代码语言:javascript复制
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();
  }

0 人点赞