前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >灵魂拷问std::enable_shared_from_this,揭秘实现原理

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

作者头像
公众号guangcity
发布2023-11-22 17:12:32
1.9K0
发布2023-11-22 17:12:32
举报
文章被收录于专栏:光城(guangcity)

灵魂拷问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();
  }
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-11-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 光城 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 灵魂拷问std::enable_shared_from_this,揭秘实现原理
    • 引言
      • std::shared_ptr基础知识
        • enable_shared_from_this类初识
          • 实现原理
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档