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

灵魂拷问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.mzph.cn/news/152471.shtml

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

相关文章

虹科示波器 | 汽车免拆检修 | 1994款凯迪拉克fleetwood车发动机无法起动

一、故障现象 一辆1994款凯迪拉克fleetwood车&#xff0c;搭载5.7L发动机&#xff08;燃油系统采用进气歧管多点喷射&#xff0c;每个气缸都有独立的喷油器&#xff1b;点火系统只有一个点火线圈&#xff0c;带机械分电器和高压线&#xff09;&#xff0c;发动机无法起动。 二、…

浅析RSA非对称加密算法

目录 引言 凯撒密码 对称加密 非对称加密 ​编辑总结 引言 几月前在知乎上看到一个关于RSA公钥与私钥加解密的提问甚感兴趣&#xff0c;却一直没有时间去探究&#xff0c;今日浅得闲时以文记之。 在文章正式开始之前先讲一个小故事&#xff0c;在公元前58年时&#xff0c…

OpenAI GPT5计划泄露

OpenAI的首席执行官萨姆奥特曼在最近接受《金融时报》的专访时&#xff0c;分享了OpenAI未来发展的一些新动向。此外&#xff0c;他还透露了关于即将到来的GPT-5模型以及公司对AGI的长期目标的一些细节。 奥特曼指出&#xff1a; 1.OpenAI正在开发GPT-5&#xff0c;一种更先进的…

原理Redis-ZipList

ZipList 1) ZipList的组成2) ZipList的连锁更新问题3) 总结 1) ZipList的组成 ZipList 是一种特殊的“双端链表” &#xff0c;由一系列特殊编码的连续内存块组成。可以在任意一端进行压入/弹出操作, 并且该操作的时间复杂度为 O(1)。 ZipListEntry: ZipList 中的Entry并不像…

怎么在echarts图上左右滑动切换数据区间

说在前面 不管前端还是后端&#xff0c;大家或多或少都了解使用过echarts图表吧&#xff0c;很多时候我们只是需要展示指定区间的数据&#xff0c;但有时我们希望在图表上能够轻松地切换数据的展示区间&#xff0c;以便更清晰地观察特定时间段或区域的变化。在本文中&#xff0…

贪吃蛇游戏制作

首先在ecilsp里面创建两个包&#xff0c;启动和图形界面 在创建一个文件夹用来放图片 1.绘制图形界面 package com.snaketuxing.view;import java.awt.Color; import java.awt.EventQueue; import java.awt.Font; import java.awt.Frame; import java.awt.Graphics; import …

win10蓝屏重启故障修复经验分享

1. 安全模式&#xff1a;尝试进入安全模式&#xff0c;按住Shift键并同时点击“重新启动”选项。然后选择“故障排除”>“高级选项”>“启动设置”>“重新启动”。在启动设置页面&#xff0c;按下F4或按4键选择进入安全模式&#xff0c;观察是否仍然出现蓝屏重启问题。…

cmake+OpenCV4.8.0+contrib4.8.0+cuda 12.2编译踩坑

cmakeOpenCV4.8.0contrib4.8.0cuda 12.2编译踩坑 准备工具 cmake &#xff08;去官网下载&#xff09;OpenCV 我下载的是官网发布最新的稳定版本对应的源码&#xff0c;官网目前是4.8.0&#xff0c;github下一个&#xff08;连不上的可以网上找找资源或者科学上网&#xff09…

听GPT 讲Rust源代码--src/bootstrap

图片来自 使用rust的image库进行图片压缩[1] File: rust/src/bootstrap/build.rs 在Rust源代码中&#xff0c;rust/src/bootstrap/build.rs这个文件是一个构建脚本。构建脚本是一个在编译Rust编译器本身时运行的程序&#xff0c;它用于初始化和配置Rust编译器的构建过程。build…

基于SSM的焦作旅游协会管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

ES6有何新特性?(下篇)

目录 函数参数的默认值设置 rest参数 扩展运算符 Symbol 迭代器 生成器 Promise Class 数值扩展 对象方法扩展 模块化 大家好呀&#xff01;今天这篇文章继续为大家介绍ES6的新特性&#xff0c;上上上篇文章介绍了一部分&#xff0c;这篇文章会将剩下的部分新增的特…

虚拟化逻辑架构: 创建KVM中的VM与实现VNC远程登录

目录 一、实验 1.安装KVM环境管理工具并创建VM&#xff08;虚拟机&#xff09; 2.Windows使用VNC Viewer连接KVM中的VM&#xff08;虚拟机&#xff09; 二、问题 1.如何下载安装VNC Viewer 一、实验 1.安装KVM环境管理工具并创建VM&#xff08;虚拟机&#xff09; (1) 采…

德迅云安全和您聊聊关于DDOS高防ip的一些方面

德迅DDoS防护服务是以省骨干网的DDoS防护网络为基础&#xff0c;结合德迅自研的DDoS攻击检测和智能防护体系&#xff0c;向您提供可管理的DDoS防护服务&#xff0c;自动快速的缓解网络攻击对业务造成的延迟增加&#xff0c;访问受限&#xff0c;业务中断等影响&#xff0c;从而…

代码随想录算法训练营第二十九天| 491 递增子序列 46 全排列

目录 491 递增子序列 46 全排列 491 递增子序列 在dfs中进行判断&#xff0c;如果path的长度大于1&#xff0c;则将其添加到res中。 本题nums中的元素的值处于-100与100之间&#xff0c;可以将元素映射0到199之间并且通过布尔数组st来记录此层中元素是否被使用过&#xff0c;…

C练习题_14

一、单项选择题&#xff08;本大题共 20小题&#xff0c;每小题 2分&#xff0c;共 40分。在每小题给出的四个备选项中&#xff0c;选出一个正确的答案&#xff0c;并将所选项前的字母填写在答题纸的相应位置上。) 以下叙述不正确的是&#xff08;&#xff09; A.一个C源程序可…

高版本Vivado和Linux 4.x内核移植Digilent Driver

移植环境 Vivado 2022.2Ubuntu 22.04petalinux 2022.2Linux内核4.14&#xff08;xilinx-linux-2018.3&#xff09;linux-digilent 主要问题 https://github.com/Digilent/linux-digilent 这些驱动支持Linux kernel release 4.x&#xff0c;然而和Vitis 2022.2 套件对应的内核…

buildadmin+tp8表格操作(8) 表格下方添加 合计行

表格的下方可以自定义添加一个合计行&#xff0c;如果有其它的需求&#xff0c; 我们可以添加我们自已需要的行&#xff0c; 并不局限于合计行 以上就可以给表格的最下方添加一个合计行了 完整代码如下 <template><div class"default-main ba-table-box"&…

AVL树你需要了解一下

AVL树介绍 AVL树是一种自平衡二叉查找树&#xff0c;它得名于发明者G.M.Adel’son-Vel’skii和E.M.Landis。AVL树的特点是任何节点的两个子树的高度最大差别为1&#xff0c;因此它也被称为高度平衡树。在AVL树中&#xff0c;每个节点的平衡因子只有-1、0、1三种&#xff0c;通…

人工智能给我们的生活带来了巨大的影响?

1. 人工智能从哪些方面给我们带来了影响&#xff1f; 人工智能出现&#xff0c;极大地影响了人类的生活&#xff0c;下面是人工智能所影响的领域&#xff1a; 1. 日常生活 智能家居: AI驱动的设备&#xff0c;如智能扬声器、灯光、恒温器&#xff0c;正在改变我们与家居环境的…

【鸿蒙最新全套教程】<HarmonyOS第一课>1、运行Hello World

下载与安装DevEco Studio 在HarmonyOS应用开发学习之前&#xff0c;需要进行一些准备工作&#xff0c;首先需要完成开发工具DevEco Studio的下载与安装以及环境配置。 进入DevEco Studio下载官网&#xff0c;单击“立即下载”进入下载页面。 DevEco Studio提供了Windows版本和…