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

news2025/1/11 8:18:01

灵魂拷问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 来产生,例如:

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继承,私有继承会发生什么?

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();
  }

更多干货文章发布于星球,欢迎加入学习~

20a4b77886118d73401941b1d065c377.jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1232316.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

中贝转债上市价格预测

中贝转债-113678 基本信息 转债名称&#xff1a;中贝转债&#xff0c;评级&#xff1a;A&#xff0c;发行规模&#xff1a;5.17亿元。 正股名称&#xff1a;中贝通信&#xff0c;今日收盘价&#xff1a;49.2元&#xff0c;转股价格&#xff1a;32.8元。 当前转股价值 转债面值…

JavaScript中的random小案例

前言 Math对象是JavaScript的内置对象&#xff0c;而random是Math对象属性 Math.random&#xff08;&#xff09;函数返回一个浮点数&#xff0c;伪随机数在范围从0 到小于1&#xff0c;也就是说&#xff0c;从 0&#xff08;包括 0&#xff09;往上&#xff0c;但是不包括 1&a…

Python编程技巧 – 使用字典

Python编程技巧 – 使用字典 Python Programming Skills – Using Dictionary Dictionary, 即字典&#xff0c;这是Python语言的一种重要的数据结构&#xff1b;Python字典是以键&#xff08;key&#xff09;值(value)对为元素&#xff0c;来存储数据的集合。 前文提到Python列…

春秋云境靶场CVE-2022-30887漏洞复现(任意文件上传漏洞)

文章目录 前言一、CVE-2022-30887描述和介绍二、CVE-2021-41402漏洞复现1、信息收集2、找可能可以进行任意php代码执行的地方3、漏洞利用找flag 总结 前言 此文章只用于学习和反思巩固渗透测试知识&#xff0c;禁止用于做非法攻击。注意靶场是可以练习的平台&#xff0c;不能随…

【电路笔记】-星三角变换(Star-Delta Transformation)

星三角变换&#xff08;Star-Delta Transformation&#xff09; 文章目录 星三角变换&#xff08;Star-Delta Transformation&#xff09;1、概述1.1 单相配置1.2 多相配置 2、三相连接2.1 Y配置2.2 Δ配置 3、Y-Δ 和 Δ-Y 变换3.1 Y-Δ变换3.2 Δ-Y变换3.3 应用 4、总结 本文…

使用ssh在本地环境(Windows)连接虚拟机以及其中的docker容器

配置虚拟机防火墙 防火墙的一系列操作需要root权限&#xff0c;默认是没有root密码的&#xff0c;所以首先需要设置root密码&#xff1a; sudo passwd root按提示完成root密码设置 切换到root账户 su root启用22端口并重启防火墙 firewall-cmd --permanent --add-port22/tc…

【zabbix监控四】zabbix之监控tomcat服务报警

一、监控tomcat服务是否正常运行 1、客户端部署 首先要在zabbix-agent客户端上安装tomcat服务&#xff0c;并能正常启动和关闭 1.1 客户端编写脚本 vim /opt/tomcat.sh#!/bin/bash anetstat -natp |grep 8080|awk {print $6}|grep LISTEN if [[ $a LISTEN ]];thenecho &qu…

【目标测距】雷达投影测距

文章目录 前言一、读取点云二、点云投影图片三、读取检测信息四、点云投影测距五、学习交流 前言 雷达点云投影相机。图片目标检测&#xff0c;通过检测框约束等等对目标赋予距离。计算消耗较大&#xff0c;适合离线验证操作。在线操作可以只投影雷达检测框。 一、读取点云 py…

HandBrake :MacOS专业视频转码工具

handbrake 俗称大菠萝&#xff0c;是一款免费开源的视频转换、压缩软件&#xff0c;它几乎支持目前市面上所能见到的所有视频格式&#xff0c;并且支持电脑硬件压缩&#xff0c;是一款不可多得的优秀软件 优点 ∙Windows, Linux, Mac 三平台支持 ∙开源、免费、无广告 ∙支…

SSM2

DataSource mybatis与Spring整合 事务加载业务层上面 开启事务驱动 上面都是声明式开启事务 图书管理系统 命名规范: java命名规范:驼峰命名法类:大驼峰变量,属性名.方法名:小驼峰 常量使用下划线分割:全大写,单词与单词之间下划线分割数据库命名规范:常用命名规范:下划线…

Python将原始数据集和标注文件进行数据增强(随机仿射变换),并生成随机仿射变换的数据集和标注文件

Python将原始数据集和标注文件进行数据增强&#xff08;随机仿射变换&#xff09;&#xff0c;并生成随机仿射变换的数据集和标注文件 前言前提条件相关介绍实验环境生成随机仿射变换的数据集和标注文件代码实现输出结果 前言 由于本人水平有限&#xff0c;难免出现错漏&#x…

基于单片机K型热电偶温度采集报警系统

**单片机设计介绍&#xff0c; 基于单片机K型热电偶温度采集报警系统 文章目录 一 概要简介系统特点系统组成工作原理应用领域 二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 # 基于单片机K型热电偶温度采集报警系统介绍 简介 该系统是基于单片…

Unity--互动组件(Scrollbar)||Unity--互动组件(DropDown )

此组件中的&#xff0c;交互&#xff0c;过渡&#xff0c;导航与文章&#xff08;Unity--互动组件&#xff08;Button&#xff09;&#xff09;中的介绍如同&#xff1b; handle rect&#xff1a;&#xff08;父节点矩形&#xff09; 用于控件的滑动“句柄”部分的图形&#xf…

Harmony Ble 蓝牙App (一)扫描

Harmony Ble 蓝牙App &#xff08;一&#xff09;扫描 前言正文一、创建工程二、工程配置① 权限配置② Debug配置③ UI配置 三、扫描① 扫描接口② 扫描类 四、业务处理① Slice的生命周期② 蓝牙开关和动态权限请求 五、扫描设备六、显示设备① 自定义蓝牙类② 提供者③ 显示…

【机器学习】037_暂退法

一、实现原理 具有输入噪音的训练&#xff0c;等价于Tikhonov正则化 核心方法&#xff1a;在前向传播的过程中&#xff0c;计算每一内部层的同时注入噪声 从作用上来看&#xff0c;表面上来说是在训练过程中丢弃一些神经元 假设x是某一层神经网络层的输出&#xff0c;是下一…

​EMNLP 2023 findings | 生成式框架下解决输入扰动槽填充任务

©PaperWeekly 原创 作者 | 回亭风 单位 | 北京邮电大学 研究方向 | 自然语言理解 论文标题&#xff1a; DemoNSF: A Multi-task Demonstration-based Generative Framework for Noisy Slot Filling Task 论文链接&#xff1a; https://arxiv.org/abs/2310.10169 代码链接…

【Linux】套接字编程

目录 套接字 IP PORT TCP和UDP的介绍 TCP UDP 网络字节序 转换接口 UDP服务器的编写 服务器的初始化 socket bind sockaddr 结构 服务器的运行 数据的收发 业务处理 客户端的编写 运行效果 拓展 套接字 &#x1f338;首先&#xff0c;我们先思考一个问题…

11.4MyBatis(基础)

一.搭环境 1.创建完SSM项目,添加MySQL和MyBatis后,项目启动一定会报错,这是正常情况. 2.配置文件 properties: server.port9090 spring.datasource.urljdbc:mysql://127.0.0.1:3306/test1?characterEncodingutf8&useSSLfalse spring.datasource.usernameroot spring.d…

Linux内核的安装

1.通过tftp 加载内核和根文件系统 即sd内存卡启动&#xff1a; SD卡的存储以扇区为单位,每个扇区的大小为512Byte, 其中零扇区存储分区表&#xff08;即分区信息&#xff09;,后续的扇区可自行分区和格式化&#xff1b; 若选择SD卡启动&#xff0c;处理器上电后从第一个扇区开…

开发仿抖音APP遇到的问题和解决方案

uni-app如何引入阿里矢量库图标/uniapp 中引入 iconfont 文件报错文件查找失败 uni-app如何引入阿里矢量库图标 - 知乎 uniapp 中引入 iconfont 文件报错文件查找失败&#xff1a;‘./iconfont.woff?t1673007495384‘ at App.vue:6_宝马金鞍901的博客-CSDN博客 将课件中的cs…