C/C++|智能指针的shared_from_this和enable_shared_from_this

news2024/12/23 13:09:20

参考博客(或者叫摘抄的博客,本博客只做为个人学习使用):
施磊老师牛逼
深入掌握C++智能指针
C++智能指针的enable_shared_from_this和shared_from_this机制

文章目录

    • 再探 shared_ptr
    • 错误一
    • 修改错误一
    • 错误二
    • 修改错误二 enable_shared_from_this和shared_from_this

再探 shared_ptr

在此之前,笔者已经写过一篇关于智能指针的博客【智能指针的实现及智能指针的浅拷贝问题:shared_ptr和weak_ptr详细解读】。
为了引出我们的主题,先再一起讨论一下 shared_ptr。

shared_ptr 智能指针都我们知道,肯定是在栈上面的,那么,现在问题来了:“shared_ptr智能指针的引用计数在哪里存放?
在shared_ptr的源码中是这样的:

private:
	/*
	下面这两个是shared_ptr的成员变量,_Ptr是指向内存资源的指针,_Rep是
	指向new出来的计数器对象的指针,该计数器对象包含了资源的一个引用计数器count
	_Ptr_base的两个成员变量,这里只罗列了_Ptr_base的部分代码
	*/
	element_type * _Ptr{nullptr};
	_Ref_count_base * _Rep{nullptr};

**很明显,shared_ptr智能指针的资源引用计数器在内存的heap堆上。**也就是说,当我们定义一个 shared_ptr<int> ptr(new int)的智能指针对象时,该智能指针对象本身的内存是:

那么把智能指针管理的外部资源以及引用计数资源都画出来的话,如下图所示:

所以说,如果我们这样操作代码:

shared_ptr<int> ptr1(new int);
shared_ptr<int> ptr2(ptr1);
cout<<ptr1.use_count()<<endl;
cout<<ptr2.use_count()<<endl;

这段代码没有任何问题,ptr1和ptr2管理了同一个资源,引用计数打印出来的都是2,出函数作用域依次析构,最终new int资源只释放一次,逻辑正确!这是因为shared_ptr ptr2(ptr1)调用了shared_ptr的拷贝构造函数(源码可以自己查看下),只是做了资源的引用计数的改变,没有额外分配其它资源,如下图所示:

错误一

如果我们这样操作代码:

int *p = new int;
shared_ptr<int> ptr1(p);
shared_ptr<int> ptr2(p);
cout<<ptr1.use_count()<<endl;
cout<<ptr2.use_count()<<endl;

shared_ptr ptr1( p )和shared_ptr ptr2( p )都调用了shared_ptr的构造函数,在它的构造函数中,都重新开辟了引用计数的资源,导致ptr1和ptr2都记录了一次new int的引用计数,都是1,析构的时候它俩都去释放内存资源,导致释放逻辑错误,如下图所示:

上面两个代码段,分别是shared_ptr的构造函数和拷贝构造函数做的事情,导致虽然都是指向同一个new int资源,但是对于引用计数对象的管理方式,这两个函数是不一样的,构造函数是新分配引用计数对象,拷贝构造函数只做引用计数增减。

相信说到这里,大家知道最开始的两个代码清单上的代码为什么出错了吧,因为每次调用的都是shared_ptr的构造函数,虽然大家管理的资源都是一样的,_Ptr都是指向同一个堆内存,但是_Ref却指向了不同的引用计数对象,并且都记录引用计数是1,出作用域都去析构,导致问题发生

修改错误一

就是在产生同一资源的多个shared_ptr的时候,通过拷贝构造函数或者赋值operator=函数进行,不要重新构造,避免产生多个引用计数对象,代码修改如下:

int main()
{
	A *p = new A(); // 裸指针指向堆上的对象

	shared_ptr<A> ptr1(p);// 用shared_ptr智能指针管理指针p指向的对象
	shared_ptr<A> ptr2(ptr1);// 用ptr1拷贝构造ptr2
	// 下面两次打印都是2,最终随着ptr1和ptr2析构,资源只释放一次,正确!
	cout << ptr1.use_count() << endl; 
	cout << ptr2.use_count() << endl;

	return 0;
}

错误二

#include <iostream>
using namespace std;
// 智能指针测试类
class A
{
public:
	A():mptr(new int) 
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
		delete mptr; 
		mptr = nullptr;
	}
	 
	// A类提供了一个成员方法,返回指向自身对象的shared_ptr智能指针。
	shared_ptr<A> getSharedPtr() 
	{ 
		/*注意:不能直接返回this,在多线程环境下,根本无法获知this指针指向
		的对象的生存状态,通过shared_ptr和weak_ptr可以解决多线程访问共享		
		对象的线程安全问题,参考施磊老师的另一篇介绍智能指针的博客*/
		return shared_ptr<A>(this); 
	}
private:
	int *mptr;
};
int main()
{
	shared_ptr<A> ptr1(new A());
	shared_ptr<A> ptr2 = ptr1->getSharedPtr();

	/* 按原先的想法,上面两个智能指针管理的是同一个A对象资源,但是这里打印都是1
	导致出main函数A对象析构两次,析构逻辑有问题*/
	cout << ptr1.use_count() << endl; 
	cout << ptr2.use_count() << endl;

	return 0;
}

代码同样有错误,A对象被析构了两次,而且看似两个shared_ptr指向了同一个A对象资源,但是资源计数并没有记录成2,还是1,不正确。

修改错误二 enable_shared_from_this和shared_from_this

案例二我们应该如何修改呢?注意我们有时候想在类里面提供一些方法,返回当前对象的一个shared_ptr强智能指针,做参数传递使用(多线程编程中经常会用到)。

首先肯定不能像上面代码清单2那样写return shared_ptr< A > ( this ) ,这会调用shared_ptr智能指针的构造函数,对this指针指向的对象,又建立了一份引用计数对象,加上main函数中的shared_ptr< A > ptr1(new A());已经对这个A对象建立的引用计数对象,又成了两个引用计数对象,对同一个资源都记录了引用计数,为1,最终两次析构对象释放内存,错误!

那如果一个类要提供一个函数接口,返回一个指向当前对象的shared_ptr智能指针怎么办?方法就是继承enable_shared_from_this类,然后通过调用从基类继承来的shared_from_this()方法返回指向同一个资源对象的智能指针shared_ptr

修改如下:

#include <iostream>
using namespace std;
// 智能指针测试类,继承enable_shared_from_this类
class A : public enable_shared_from_this<A>
{
public:
	A() :mptr(new int)
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
		delete mptr;
		mptr = nullptr;
	}

	// A类提供了一个成员方法,返回指向自身对象的shared_ptr智能指针
	shared_ptr<A> getSharedPtr()
	{
		/*通过调用基类的shared_from_this方法得到一个指向当前对象的
		智能指针*/
		return shared_from_this();
	}
private:
	int *mptr;
};
int main()
{
	shared_ptr<A> ptr1(new A());
	shared_ptr<A> ptr2 = ptr1->getSharedPtr();

	// 引用计数打印为2
	cout << ptr1.use_count() << endl;
	cout << ptr2.use_count() << endl;

	return 0;
}

这里简述一下 shared_from_this()的作用吧。(更详细的流程在C++智能指针的enable_shared_from_this和shared_from_this机制)

  • 首先,我们在 enable_shared_from_this<A> 基类中继承一个成员变量 _Wptr,当定义第一个智能指针对象的时候: shared_ptr< A > ptr1(new A()),调用 shared_ptr 的普通构造函数,就会初始化A对象的成员变量 _Wptr,作为观察A对象资源的一个弱智能指针观察者。
  • 其次,我们看一下 shared_from_this()函数,他其实就是直接返回了一个 shared_ptr<_Ty>(_Wptr),该语法在 shared_ptr中也有相应的构造函数,其主要作用就是把一个弱智能指针提升为一个强智能指针,可以在多线程环境中判断对象是否存活或者已经析构释放。
  • 在一个我们可以看到,在整个过程中我们都没有再次使用 shared_ptr 的普通构造函数,也就是说没有在产生额外的引用计数对象,不会存在把一个内存资源,进行多次计数的过程
  • 再次强调,请去阅读施磊老师的博客C++智能指针的enable_shared_from_this和shared_from_this机制

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

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

相关文章

web项目规范配置(husky、eslint、lint-staged、commit)

背景&#xff1a; 团队开发为了保证提交代码格式统一&#xff0c;通常在进行代码提交的时候对暂存区代码进行校验&#xff0c;如没有通过eslint(本例使用eslint)校验&#xff0c;则不能提交到远端。 安装依赖 husky 、eslint 、prettier 、lint-staged npm install husky e…

光环云携手火山引擎共推全栈AI服务,赋能千行百业智能化转型,助力新质生产力发展

5月15日&#xff0c;2024春季火山引擎FORCE原动力大会在北京举办。作为智算云网综合服务提供商&#xff0c;光环云受邀出席大会&#xff0c;与火山引擎共同探索大模型时代下行业发展的新趋势。 会上&#xff0c;光环云数据有限公司正式与火山引擎签署生态伙伴合作协议&#xf…

算法与数据结构:红黑树

ACM大牛带你玩转算法与数据结构-课程资料 本笔记属于船说系列课程之一&#xff0c;课程链接&#xff1a; 哔哩哔哩_bilibilihttps://www.bilibili.com/cheese/play/ep66799?csourceprivate_space_class_null&spm_id_from333.999.0.0 你也可以选择购买『船说系列课程-年度会…

计算机图形学入门04:视图变换

1.MVP变换 将虚拟场景中的模型投影到屏幕上&#xff0c;也就是二维平面上&#xff0c;需要分三个变换。 1.首先需要知道模型的位置&#xff0c;也就是前面提到的基本变换&#xff0c;像缩放、平移&#xff0c;旋转&#xff0c;也称为模型(Model)变换。 2.然后需要知道从…

精选免费在线工具与资源推荐20240531

精选免费在线工具与资源推荐 引言 在互联网高速发展的今天&#xff0c;我们身处一个信息爆炸的时代。为了更好地应对工作和学习中的挑战&#xff0c;我们时常需要借助各种工具和资源来提高效率。幸运的是&#xff0c;网络上存在着大量免费且高效的在线工具和资源&#xff0c;…

告别低效提问:掌握BARD技巧,让AI成为你的智能助手!

今天只聊一个主题&#xff1a;提示词 Prompt。 说到提示词&#xff0c;大家可能都看过GPT的高级示例&#xff0c;那些几百字的提示词&#xff0c;写起来确实不容易。 那么&#xff0c;如何写出同样效果的提示词呢&#xff1f; 有没有什么公式或者系统学习的方法&#xff1f;…

HackTheBox-Machines--Nibbles

Nibbles 测试过程 1 信息收集 NMAP 80 端口 网站出了打印出“Hello world&#xff01;”外&#xff0c;无其他可利用信息&#xff0c;但是查看网页源代码时&#xff0c;发现存在一个 /nibbleblog 文件夹 检查了 http://10.129.140.63/nibbleblog/ &#xff0c;发现了 /index.p…

windows系统配置dns加快访问github 实用教程一(图文保姆级教程)

第一步、打开网页 https://tool.lu/ip IP地址查询 - 在线工具 输入www.github.com 或者github.com 点击网页查询按钮, 获取对应github网站对应的ip 完整操作步骤如上图所示,可以很清晰的看到github网站的ip显示地区是美国也就是说该网站服务器是在国外, 这也就是为什么我们在…

JUC总结2

synchronized锁 synchronized底层原理 当使用synchronized时&#xff0c;不需要自己编写代码进行上锁和上锁的操作&#xff0c;因为JVM帮我们把相关操作完成了。 JVM采用了monitorenter和monitorexit指令进行同步的&#xff0c;前者指向同步代码开始的位置&#xff0c;后者指…

java——网络原理初识

T04BF &#x1f44b;专栏: 算法|JAVA|MySQL|C语言 &#x1faf5; 小比特 大梦想 目录 1.网络通信概念初识1.1 IP地址1.2端口号1.3协议1.3.1协议分层协议分层带来的好处主要有两个方面 1.3.2 TCP/IP五层 (或四层模型)1.3.3 协议的层和层之间是怎么配合工作的 1.网络通信概念初识…

探索气象数据的多维度三维可视化:PM2.5、风速与高度分析

探索气象数据的多维度可视化&#xff1a;PM2.5、风速与高度分析 摘要 在现代气象学中&#xff0c;数据可视化是理解复杂气象模式和趋势的关键工具。本文将介绍一种先进的数据可视化技术&#xff0c;它能够将PM2.5浓度、风速和高度等多维度数据以直观和动态的方式展现出来。 …

国产身份域管架构图集合(信创政策AD域替换必看)

几类典型架构 双机架构 单点单机房 集群架构 多点单机房 两地三中心架构 多点多机房 多地分布式架构 多点多机房 全栈信创方案架构&#xff0c;欢迎探讨交流~

emp.dll文件丢失要怎么解决?荒野大镖客emp.dll修复方法分享

软件运行过程中经常遇到各种技术问题&#xff0c;其中之一就是动态链接库&#xff08;DLL&#xff09;文件丢失的现象。DLL文件是Windows操作系统中一个重要的组件&#xff0c;它包含运行多个应用程序所需要的代码和数据。因此&#xff0c;一个丢失的DLL文件&#xff0c;如“em…

同城活动报名系统源码活动组局找搭子小程序Java源码全开源

活动流程图 管理端设置 1.系统操作 2.活动类型 可添加线上和线下活动,线上活动,比如游戏等,需要可以进入游戏,需要签到等; 线下活动,比如线下交友等, 3.活动管理 可给用户添加活动,给活动设置报名时间,活动开始时间等; 也可查看报名列表和签到列表 4.进行中的活动 等发起…

校园导航系统C++

制作一个简单的大学城导航系统&#xff0c;根据用户指定的起点和终点&#xff0c;求出最短路径长度以及具体路径。 项目要求&#xff1a; 1&#xff09;程序与数据相分离&#xff0c;地图中的所有数据都是从文件读入&#xff0c;而不是写在代码中 2&#xff09;最短路径算法…

热敏电阻的设计

热敏电阻(NTC)的作用&#xff1a;抑制开机时的浪涌电流。防止开机瞬间产生的浪涌电流损坏后面的元件。 取值依据:根据对开机的脉冲电流&#xff08;浪涌电流&#xff09;小于多少A&#xff1f; 由,这个U是指最大输入电压&#xff0c;I为要求的浪涌电流。 NTC是负温度系数的热…

设计模式23——状态模式

写文章的初心主要是用来帮助自己快速的回忆这个模式该怎么用&#xff0c;主要是下面的UML图可以起到大作用&#xff0c;在你学习过一遍以后可能会遗忘&#xff0c;忘记了不要紧&#xff0c;只要看一眼UML图就能想起来了。同时也请大家多多指教。 状态模式&#xff08;State&am…

打造高效上传体验:基于Kotlin的Android快速上传框架

1. 引言 在Android开发中&#xff0c;文件上传操作常常面临各种挑战&#xff0c;为此我开源了一个高效、易用的快速上传框架&#xff0c;助力开发者轻松实现文件上传功能。 GitHub项目地址: 点我 2. 框架特点概述 纯Kotlin编写&#xff1a;简洁、现代的编程语言。MVVM架构&a…

动态分配函数参数用二级指针的作用

文章目录 前言一、案例 前言 在一些情况下&#xff0c;我们需要在函数内部动态地分配内存来存储结构体&#xff0c;并且需要在函数外部访问该结构体。在这种情况下&#xff0c;可以使用二级指针作为函数参数来实现动态内存分配&#xff0c;并且在函数外部使用指针访问结构体。…

py黑帽子学习笔记_web攻击

python网络库 py2的urllib2 py3好像把urllib2继承到了标准库urllib&#xff0c;直接用urllib就行&#xff0c;urllib2在urllib里都有对应的接口 py3的urllib get请求 post请求&#xff0c;和get不同的是&#xff0c;先把post请求数据和请求封装到request对象&#xff0c;再…