《白话C++》第10章 STL和boost,Page105 enable_shared_from_this

说到“循环引用”,其中“自己对自己”的引用是最直接的循环引用,如图10-12所示。

而说到“自己”,在C++语言中应该首先想到的类的“this”指针。不过,this指针是裸指针,如果我们在类中,需要传递当前对象本身,可是接受方却只能接受本类的智能指针,那该怎么办呢?比如前面提到的按个函数:

Coo::Ptr foo(int a, Coo::Ptr pcoo)
{
    ...
}

foo函数有个入参必须是shared_ptr <Coo> 类型,假设在Coo中需要调用该函数:

class Coo
{
public:
    typedef std::shared_ptr <Coo> Ptr;

    void Test()
    {
        foo(123, ______);
        ...
    }
...
};

下划线处,该填写什么呢?填this? foo不接受,编译失败。

既然this是裸指针,那就从它身上创建一个shared_ptr呗:

void Test()
{
    shared_ptr <Coo> shared_this(this);
    foo(123, shared_this);
    ...
}

编译,成功!解决问题易如反掌啊,可惜乐得太早了,有大问题发生了。

先看看,如果类中this所代表的当前Coo对象,是这么创建的:

//灾难一:
int main()
{
    Coo coo_1; //coo_1是个栈对象
    coo_1.Test();
}

 使用coo_1调用成员函数Test()时,Test()中的this就是coo_1。coo_1是一个栈对象,它会在main()函数结束时自动释放。然而,foo()函数接过去的那个shared_ptr对象,它也会尝试释放所拥有的裸指针,也就是this,而  *this就是coo_1,而coo_1……它是一个会自动释放的栈对象!一个对象会被释放两次就是灾难,如果这个对象还是栈对象,那就是史诗般的灾难了。大家可以试试,

要不小心点,使用堆对象吧:

//灾难二:
int main()
{
    Coo* coo_2 = new Coo; //coo_2是个堆对象
    coo_2->Test();
    //coo_2->Test();
}

现在的问题是,main()函数中要不要主动delete coo_2?如果释放,就会发生重复释放的问题,如果不释放,有谁能从这几行代码中看出,当coo_2第一次调用了Test()之后,coo_2就很可能“挂掉了”呢?如果有人又调用Test()一次,他知道其实coo_2是一个尸体吗?

还好《白话C++》教会我们,如果一个类的对象需要用到shared_ptr,那就在一开始绑定shared_ptr,所以,第三个版本的灾难片呼啸而来:

//灾难三:
int main()
{
    Coo::Ptr coo_3 = make_shared <Coo> ();
    coo_3->Test();
}

因为绑定了shared_ptr,所以背地里所创建的Coo堆对象肯定会被释放啦,然而一运行程序,世界还是崩溃了。

问:出现过几个shared_ptr在共同管理背后所创建的Coo堆对象?

答:两个,一个是coo_3,另一个是Test()成员函数中的shared_this。

再问:那么,shared_this是从前一个智能指针创建出来的吗?

答:不是,它是从裸指针this创建出来的。

只要是从裸指针创建出来的,无论是来自this,还是来自某个智能指针的get()操作,都会让新建的智能指针,以为自己是这个裸指针的第一个保护者……

真相大白,罪魁祸首正是this这个裸指针。每次我们用this创建一个新的shared_ptr,对裸指针的计数管理都会从新从1开始。

this在关键时刻掉了链子,造成一堆管理同一对象却互不引用的shared_ptr

手工解决的话,直觉上可能会为该类添加一个“shared_ptr”版本的this成员数据:

class Coo
{
    ...
private:
    shared_ptr <Coo> _shared_this;
};

但应该立刻清醒过来,这是在“自己引用自己”!所以应该换成weak_ptr:

​class Coo
{
    ...
private:
    weak_ptr <Coo> _weak_this;
};

怎么初始化这个成员呢?干脆提供一个接口:

class Coo
{
    ...
public:
    void SetWeakThis(shared_ptr <Coo> shared_this)
    {
        _weak_this = shared_this;
    }
private:
    weak_ptr <Coo> _weak_this;
};

可想而知,每次创建C的对象(智能指针)时,都得马上调用SetWeakThis():

...
shared_ptr <Coo> sp = make_shared <Coo> ();
sp->SetWeakThis(sp);

这样的代码又麻烦又丑陋。还好,标准库为我们提供了一个工具类,它有一个很长但直观的名字,叫“enable_shared_from this(允许从自身共享)”,严格上讲是一个类模板。使用方法是将它作为基类,并将当前类作为它的模板入参。

很明显,派生它是希望继承它的名字所表达的能力,所以很适合采用私用派生:

class Coo : std::enabled_shared_from_this <Coo>
{
public:
    void Test()
    {
        foo(123, shared_from_this());
        ...
    }
};

注意尖括号中的Coo。再注意加粗的“shared_from_this()”,这就是我们继承所得的能力,它保障传递给foo一个正确的shared_ptr<Coo>。测试一下:

//正确:
int main()
{
    Coo::Ptr coo_3 = make_shared <Coo> ();
    coo_3->Test();
}

一切都很正确,不存在重复释放。

让Coo认enable_shared_from_this<Coo>为父,更是注定Coo必须以智能指针面试,否则程序在运行时将因调用“shared_from_this()”而出错,原因可参考之前的分析,现在先牢记结论:

//运行:程序崩溃

int main()
{
    Coo coo_1; 
    coo_1.Test();
}

//运行:程序崩溃

int main()
{

    //普通堆对象
    Coo* coo_2 = new Coo; 
    coo_2->Test();
}

//运行正常

int main()
{
    Coo::Ptr coo_3 = make_shared <Coo> ();
    coo_3->Test();
}

【课堂作业】:指定要求的二叉树定义

请使用shared_ptr结合enable_shared_from_this等技术,实现一颗“二叉树”结构的定义。二叉树由节点组成。要求每个节点可以拥有一左一右的两个子节点,或者不拥有子节点(称为叶子节点),节点还需要拥有一个指针,指向父节点,最顶层的节点称为“跟节点”,它的父节点为空。要求提供从根节点开始,逐步添加左右节点以生成一棵树的功能。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【嵌入式-Keil】keil代码提示快捷键

CTRL空格 如果没有提示&#xff0c;可能跟输入法的快捷键冲突&#xff0c; 右键->设置->按键->勾掉第一个就行了 再按CTRL空格就有提示了 参考&#xff1a;串口发送&串口发送接收

Vue | (三)使用Vue脚手架(中)| 尚硅谷Vue2.0+Vue3.0全套教程

文章目录 &#x1f4da;Todo-list 案例&#x1f407;组件化编码流程&#xff08;通用&#xff09;&#x1f407;实现静态组件&#x1f407;展示动态数据&#x1f407;交互⭐️添加一个todo⭐️todo勾选实现⭐️删除功能实现⭐️底部统计功能实现⭐️底部全选功能实现⭐️底部一…

【黑马程序员】C++文件操作

20240220 文章目录 文件操作背景文件分类操作文件的三大类 文本文件写文件写文件步骤文件打开方式代码示例 读文件读文件步骤代码示例 写二进制文件写二进制文件步骤代码示例 读二进制文件代码示例 文件操作 背景 程序运行时产生的数据都属于临时数据&#xff0c;程序一旦运行…

TypeScript(三):TypeScript面向对象

TypeScript面向对象 类的定义 与JS不同的是&#xff0c;成员属性需要在前面进行提前声明 class Person{//需要在前面对成员变量进行声明name: string//声明的时候&#xff0c;可以对值进行初始化&#xff0c;初始化可以带有类型注解&#xff0c;也可以省略age 18//construc…

基于YOLOv7算法和Widerperson数据集的高精度实时行人检测系统(PyTorch+Pyside6+YOLOv7)

摘要&#xff1a;基于YOLOv7算法和Widerperson数据集的高精度实时行人检测系统可用于日常生活中检测与定位行人目标&#xff0c;此系统可完成对输入图片、视频、文件夹以及摄像头方式的目标检测与识别&#xff0c;同时本系统还支持检测结果可视化与导出。本系统采用YOLOv7目标检…

3个密码学相关的问题

一、离散对数问题&#xff08;Discrete Logarithm Problem, DLP&#xff09; 问题描述&#xff1a;给定 有限阿贝尓群 G中的2个元素a和b&#xff0c;找出最小的正整数x满足&#xff1a;b a ^^ x &#xff08;或者证明这样的x不存在&#xff09;。 二、阶数问题&#xff08;O…

云服务器ECS价格表出炉——阿里云

2024年阿里云服务器租用价格表更新&#xff0c;云服务器ECS经济型e实例2核2G、3M固定带宽99元一年、ECS u1实例2核4G、5M固定带宽、80G ESSD Entry盘优惠价格199元一年&#xff0c;轻量应用服务器2核2G3M带宽轻量服务器一年61元、2核4G4M带宽轻量服务器一年165元12个月、2核4G服…

[element] el-upload实现 “读取本地表格内容并上传“

需求: 通过表格一键导入数据 表格模板: 导入按钮: <el-uploadref"upload"class"filter-item"style"margin-left: 10px"action"/"accept".csv, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.sp…

Open3D三维重建

原始点云&#xff1a; alpha_shape算法 import open3d as o3dpcd o3d.io.read_point_cloud("airplane_0001.pcd") mesh o3d.geometry.TriangleMesh.create_from_point_cloud_alpha_shape(pcd, alpha0.1) o3d.visualization.draw_geometries([mesh], mesh_show_b…

相机图像质量研究(39)常见问题总结:编解码对成像的影响--运动模糊

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结&#xff1a;光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结&#xff1a;光学结构对成…

我把ChatGPT部署到我的手机上

正常的大模型部署都是在服务器上的 但是最近我看到一个手机上可以运行的大模型 分享给大家 MiniCPM MiniCPM是基于 MLC-LLM 开发&#xff0c;将 MiniCPM 和 MiniCPM-V 在 Android 手机端上运行。 使用起来很简单&#xff0c;下载好安装包后 按照教程安装好 下载2个模型 一个是M…

C++拷贝构造函数与赋值运算符重载

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、拷贝构造函数 1.概念 在现实生活中&#xff0c;可能存在一个与你一样的自己&#xff0c;我们称其为双胞胎。 那在创…

虹科方案丨低负载ECU老化检测解决方案:CANCAN FD总线“一拖n”

来源&#xff1a;虹科汽车智能互联 虹科方案丨低负载ECU老化检测解决方案&#xff1a;CANCAN FD总线“一拖n” 原文链接&#xff1a;https://mp.weixin.qq.com/s/4tmhyE5hxeLFCiaeoRhlSg 欢迎关注虹科&#xff0c;为您提供最新资讯&#xff01; #汽车总线 #ECU #CAN卡 导读 …

配置Python环境及job运行的虚拟环境

1、配置Jenkins的Python环境&#xff1a;Manage Jnekins-Global Tool Configuration-Python 2、安装pyenv插件 此插件会给每个job都创建一个虚拟Python环境 安装后&#xff0c;在job config-build中选择 virtualenv builder build job的时候会自动在/opt/jenkins(node主机的…

详解平面点云面积计算

部分代码展示&#xff1a; &#xff08;1&#xff09;利用格网法计算面积&#xff1a; //&#xff08;2&#xff09;测试使用格网法计算平面点云面积 void main() {char *inputpath "D:\\testdata\\data.txt";vector<pcl::PointXYZ> points ReadPointXYZIn…

vue的十大面试题详情

1 v-show与v-if区别 v-if与v-show可以根据条件的结果,来决定是否显示指定内容&#xff1a; v-if: 条件不满足时, 元素不会存在. v-show: 条件不满足时, 元素不会显示(但仍然存在). <div id"app"><button click"show !show">点我</but…

【动态规划专栏】专题二:路径问题--------6.地下城游戏

本专栏内容为&#xff1a;算法学习专栏&#xff0c;分为优选算法专栏&#xff0c;贪心算法专栏&#xff0c;动态规划专栏以及递归&#xff0c;搜索与回溯算法专栏四部分。 通过本专栏的深入学习&#xff0c;你可以了解并掌握算法。 &#x1f493;博主csdn个人主页&#xff1a;小…

【PX4-AutoPilot教程-TIPS】Gazebo仿真环境昏暗的解决办法即Ubuntu系统安装NVIDIA显卡驱动方法

Gazebo仿真环境昏暗的解决办法即Ubuntu系统安装NVIDIA显卡驱动方法 分析原因手动安装方法&#xff08;推荐&#xff09;自动安装方法检查是否安装成功Gazebo仿真环境前后对比 分析原因 具体原因为&#xff1a;大多数情况是因为显卡性能不足&#xff0c;Gazebo自动关闭了灯光和…

线性规划求解点云最大内接圆

欢迎关注更多精彩 关注我&#xff0c;学习常用算法与数据结构&#xff0c;一题多解&#xff0c;降维打击。 本期话题&#xff1a;利用线性规划求解点云最大内接圆 参考资料&#xff1a; Tschebyscheff approximation for the calculation of maximum inscribed minimum circ…

风云温商在湖北:黄卓仁会长的商业传奇

黄卓仁,一位来自柳市的传奇人物,他的人生就像一部精彩纷呈的商业传奇,充满了挑战与机遇。他是1966年出生的优秀民营企业家,也是一位充满激情与智慧的领导者。今天,让我们一起走进黄卓仁的世界,感受他那不凡的人生历程。 首先,让我们了解一下黄卓仁的基本情况。他是温州人,出生…