C++智能指针(3/3)

news2024/11/12 15:20:38

目录

上一节内容

share_ptr用法

share_ptr指针可以用于上一节所说的错误

 例子(类定义)

主函数代码

执行的结果

解释说明

share_ptr 相关构造

空的share指针可以指向其他相同类型的变量来进行托管

可以shared_ptr< T > sp2(new T())也可以shared_ptr< T > sp2(sp1)

shared_ptr sp4; 空的shared_ptr,指向类型为T[]的数组对象(C++17之后支持)[]>

shared_ptr sp5(new T[] { … }); 指向类型为T的数组对象(C++17之后支持)[]>

shared_ptr< T > sp6(NULL, D()); //空的shared_ptr,接受一个D类型的删除器,使用D释放内存

shared_ptr< T > sp7(new T(), D()); //定义shared_ptr,指向类型为T的对象,接受一个D类型的删除器,使用D删除器来释放内存

share_ptr初始化

share_ptr的构造函数

使用make_shared 初始化对象,分配内存效率更高(推荐使用)

赋值

 主动释放

重置指针

交换指针的值

share_ptr使用陷阱

原因

解决办法之一

weak_ptr智能指针(弱指针)

weak智能指针的用法

注意

弱指针到共享指针的转化

特别感谢


上一节内容

C++智能指针(2/3)_木木em哈哈的博客-CSDN博客自动释放内存:智能指针使用了RAII(资源获取即初始化)的原则,在创建时分配内存,在销毁时自动释放内存,无需手动管理内存释放,避免了因为忘记释放内存而造成的内存泄漏问题。unique_ptr是一种独占所有权的智能指针,同一时间只能有一个unique_ptr指向一个对象,当unique_ptr被销毁时,对象也会被释放。在这之中开始时p1托管str的指针,后面p2接管str指针的同时会把p1的托管给取消,这样p1指针指向的就是NULL(空),从而报错。这是因为auto_ptr与unique_ptr的排他性。https://blog.csdn.net/mumuemhaha/article/details/131689322?spm=1001.2014.3001.5502这一节我们来学最后的share_ptr和weak_ptr

share_ptr用法

shared_ptr使用引用计数的方式来管理资源,即每个shared_ptr对象都有一个关联的计数器,记录有多少个shared_ptr对象共享同一块内存资源。当计数器为0时,资源会被自动释放。

当复制或拷贝时,引用计数加1,当智能指针析构时,引用计数减1,如果计数为零,代表已经没有指针指向这块内存,那么我们就释放它!这就是 shared_ptr 采用的策略!

share_ptr指针可以用于上一节所说的错误

 例子(类定义)

class Person {
public:
	Person(int v) {
		this->no = v;
		cout << "构造函数 \t no = " << this->no << endl;
	}

	~Person() {
		cout << "析构函数 \t no = " << this->no << endl;
	}

private:
	int no;
};

主函数代码

int main()
{
    //定义两个share指针同时第二个由于已经构造好了,引用次数+1
	shared_ptr<Person> sp1;

	shared_ptr<Person> sp2(new Person(2));

	// 获取智能指针管控的共享指针的数量	use_count():引用计数
	cout << "sp1引用计数 = " << sp1.use_count() << endl;
	cout << "sp2引用计数 = " << sp2.use_count() << endl << endl;

	// 共享同时也共享引用次数
	sp1 = sp2;
    //打印出来引用次数同时验证猜想
	cout << "sp1引用计数 = " << sp1.use_count() << endl;
	cout << "sp2引用计数 = " << sp2.use_count() << endl << endl;
    
	shared_ptr<Person> sp3(sp1);
	cout << "sp1引用计数 = " << sp1.use_count() << endl;
	cout << "sp2引用计数 = " << sp2.use_count() << endl;
	cout << "sp2引用计数 = " << sp3.use_count() << endl << endl;

	return 0;
}

执行的结果

构造函数         no = 2
sp1引用计数 = 0
sp2引用计数 = 1

sp1引用计数 = 2
sp2引用计数 = 2

sp1引用计数 = 3
sp2引用计数 = 3
sp2引用计数 = 3

析构函数         no = 2

D:\C++\调试\x64\Debug\调试.exe (进程 1536)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .

解释说明

注释中说的很明白了,只要引用了share_ptr所指向的变量,他的引用计数就会加一

同时在函数中sp1,sp2,sp3共同管理一个share_ptr指针

share_ptr 相关构造

空的share指针可以指向其他相同类型的变量来进行托管

shared_ptr<Person> sp1;
Person *person_1 = new Person(1);
sp1.reset(person_1);	// 托管person1

可以shared_ptr< T > sp2(new T())也可以shared_ptr< T > sp2(sp1)

shared_ptr<Person> sp2(new Person(2));
shared_ptr<Person> sp3(sp1);

shared_ptr<T[]> sp4; 空的shared_ptr,指向类型为T[]的数组对象(C++17之后支持)

shared_ptr<Person[]> sp4;

shared_ptr<T[]> sp5(new T[] { … }); 指向类型为T的数组对象(C++17之后支持)

shared_ptr<Person[]> sp5(new Person[5] { 1, 2, 3, 4, 5 });

创建一个有五个Person元素的数组分别为1,2,3,4,5也就是Person(1),Person(2)......

shared_ptr< T > sp6(NULL, D()); //空的shared_ptr,接受一个D类型的删除器,使用D释放内存

假设删除函数为

class Person {
public:
	Person(int v) {
		this->no = v;
		cout << "构造函数 \t no = " << this->no << endl;
	}

	~Person() {
		cout << "析构函数 \t no = " << this->no << endl;
	}

private:
	int no;
};

// 仿函数,内存删除
class DestructPerson {
public:
	void operator() (Person *pt) {
		cout << "DestructPerson..." << endl;
		delete pt;
	}
};

则用法为

shared_ptr<Person> sp6(NULL, DestructPerson());

shared_ptr< T > sp7(new T(), D()); //定义shared_ptr,指向类型为T的对象,接受一个D类型的删除器,使用D删除器来释放内存

shared_ptr<Person> sp7(new Person(8), DestructPerson());

share_ptr初始化

share_ptr的构造函数

shared_ptr<Person> up1(new Person(10));  // Person(10) 的引用计数为1
shared_ptr<Person> up2(up1);  // 使用智能指针up1构造up2, 此时Person(10) 引用计数为2

使用make_shared 初始化对象,分配内存效率更高(推荐使用)

shared_ptr<Preson> up3 = make_shared<Preson>(2); // 多个参数以逗号','隔开,最多接受十个

赋值

shared_ptrr<int> up1(new int(10));  // int(10) 的引用计数为1
shared_ptr<int> up2(new int(11));   // int(11) 的引用计数为1
up1 = up2;	// 因为为赋值,故int(10) 的引用计数减1,计数归零内存释放,up2共享int(11)给up1, int(11)的引用计数为2

 主动释放

shared_ptrr<int> up1(new int(10));
up1 = nullptr ;	// int(10) 的引用计数减1,计数归零内存释放 
// 或
up1 = NULL; // 作用同上 

重置指针

p.reset() ; 将p重置为空指针,所管理对象引用计数 减1
p.reset(p1); 将p重置为p1(的值),p 管控的对象计数减1,p接管对p1指针的管控
p.reset(p1,d); 将p重置为p1(的值),p 管控的对象计数减1并使用d作为删除器
p1是一个指针!

交换指针的值

std::swap(p1,p2); // 交换p1 和p2 管理的对象,原对象的引用计数不变
p1.swap(p2);    // 交换p1 和p2 管理的对象,原对象的引用计数不变

share_ptr使用陷阱

小心因循环引用造成无法释放资源!

如下代码:
Boy类中有Girl的智能指针;
Girl类中有Boy的智能指针;
当他们交叉互相持有对方的管理对象时…(借用大佬写的代码)

#include <iostream>
#include <string>
#include <memory>

using namespace std;

class Girl;

class Boy {
public:
	Boy() {
		cout << "Boy 构造函数" << endl;
	}

	~Boy() {
		cout << "~Boy 析构函数" << endl;
	}

	void setGirlFriend(shared_ptr<Girl> _girlFriend) {
		this->girlFriend = _girlFriend;
	}

private:
	shared_ptr<Girl> girlFriend;
};

class Girl {
public:
	Girl() {
		cout << "Girl 构造函数" << endl;
	}

	~Girl() {
		cout << "~Girl 析构函数" << endl;
	}

	void setBoyFriend(shared_ptr<Boy> _boyFriend) {
		this->boyFriend = _boyFriend;
	}

private:
	shared_ptr<Boy> boyFriend;
};


void useTrap() {
	shared_ptr<Boy> spBoy(new Boy());
	shared_ptr<Girl> spGirl(new Girl());

	// 陷阱用法
	spBoy->setGirlFriend(spGirl);
	spGirl->setBoyFriend(spBoy);
	// 此时boy和girl的引用计数都是2
}


int main(void) {
	useTrap();

	system("pause");
	return 0;
}

这里是一个boy里有个girl的指针,而girl里也有个boy的指针

到最后

Boy 构造函数
Girl 构造函数
请按任意键继续. . .

空间都没有释放出来(没有析构函数)

原因

 这里由于都各有对方的类,故引用计数为2

但是当程序结束的时候

 函数中的智能指针清理掉了,但是类中嵌套的智能指针无法释放,所有类无法调用析构函数

解决办法之一

如果不用两个类中的指针互相关联,而是一方单方面获得管理对方的共享指针,这样可以正常释放

假设还是上面的那个例子

由于因为开始释放boy是引用次数为2,函数结束计数减一变为一

之后开始释放girl,girl引用次数为1,函数结束计数减一变为零

这是开始析构girl,同时就会把boy类也析构掉,计数再次减一

这是boy引用次数为零,则boy也开始析构了

void useTrap() {
	shared_ptr<Boy> spBoy(new Boy());
	shared_ptr<Girl> spGirl(new Girl());

	// 单方获得管理
	//spBoy->setGirlFriend(spGirl);
	spGirl->setBoyFriend(spBoy);	
}

weak_ptr智能指针(弱指针)

weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。 同时weak_ptr 没有重载*和->但可以使用 lock 获得一个可用的 shared_ptr 对象。

weak智能指针的用法

weak_ptr的定义空指针(weak_ptr <T>  a)和共享构造(weak_ptr <T> a(b)以及允许共享指针赋值给弱指针(a=b)都和其他智能指针差不多

同时和share_ptr一样可以用 .count()来查看引用次数

注意

弱指针不支持 * 和 -> 对指针的访问

弱指针到共享指针的转化

弱指针到共享指针的转化可以用.lock()来表示

例如

shared_ptr<Girl> sp_girl;
sp_girl = wpGirl_1.lock();

// 使用完之后,再将共享指针置NULL即可
sp_girl = NULL;

上面share_ptr发生的问题的另一种解决办法

#include <iostream>
#include <string>
#include <memory>

using namespace std;

class Girl;

class Boy {
public:
	Boy() {
		cout << "Boy 构造函数" << endl;
	}

	~Boy() {
		cout << "~Boy 析构函数" << endl;
	}

	void setGirlFriend(shared_ptr<Girl> _girlFriend) {
		this->girlFriend = _girlFriend;


		// 在必要的使用可以转换成共享指针
		shared_ptr<Girl> sp_girl;
		sp_girl = this->girlFriend.lock();

		cout << sp_girl.use_count() << endl;
		// 使用完之后,再将共享指针置NULL即可
		sp_girl = NULL;
	}

private:
	weak_ptr<Girl> girlFriend;
};

class Girl {
public:
	Girl() {
		cout << "Girl 构造函数" << endl;
	}

	~Girl() {
		cout << "~Girl 析构函数" << endl;
	}

	void setBoyFriend(shared_ptr<Boy> _boyFriend) {
		this->boyFriend = _boyFriend;
	}

private:
	shared_ptr<Boy> boyFriend;
};


void useTrap() {
	shared_ptr<Boy> spBoy(new Boy());
	shared_ptr<Girl> spGirl(new Girl());

	spBoy->setGirlFriend(spGirl);
	spGirl->setBoyFriend(spBoy);
}


int main(void) {
	useTrap();

	system("pause");
	return 0;
}

再次感谢大佬的代码

在这一串代码中主要变的就是各两个类中的对方函数的指针变为了弱指针(在需要的时候用.lock()给变回共享指针,使用完后置空)

这样就避免了双方都没有权限去析构对方类

特别感谢

(本章中用了不少这位大佬的代码,如有不妥,联系改正)

C++ 智能指针 - 全部用法详解_cpp智能指针特性_cpp_learners的博客-CSDN博客血的教训?不学智能指针,本人丢了一份工作。_cpp智能指针特性https://blog.csdn.net/cpp_learner/article/details/118912592

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

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

相关文章

RocketMQ高阶使用

RocketMQ高阶使用 1. 流程 2. 探讨功能点 RocketMQ的顺序消息消息投递策略消息保障 3. 顺序消息 3.1 顺序类型 3.1.1 无序消息 无序消息也指普通的消息&#xff0c;Producer 只管发送消息&#xff0c;Consumer 只管接收消息&#xff0c;至于消息和消息之间的顺序并没有保证…

macOS搭建C++开发环境CLion

首先我是一个java开发者&#xff0c;最近对C产生点兴趣。想开发点C程序玩一玩。 下载IDE 本人是java开发者&#xff0c;习惯使用IDEA了。所以也下载jetbrains的C开发工具:clion 下载地址&#xff1a; https://www.jetbrains.com/clion/download/#sectionmac Hello world Fi…

利用ArcGIS Pro制作三维效果图

1、新建工程 打开Arcgispro,新建工程,这里我们要用到的模板为全局场景。 2、添加数据 这里添加的数据需要有一个字段内容是数值的,这个字段也是接下来要进行拉伸的字段。 3、高度拉伸 数据添加进来后,如下图所示,这时图层处于2D图层里。 这时我们点中该图层,回到菜单栏…

微服务系列文章 之SpringBoot之定时任务详解

序言 使用SpringBoot创建定时任务非常简单&#xff0c;目前主要有以下三种创建方式&#xff1a; 一、基于注解(Scheduled)二、基于接口&#xff08;SchedulingConfigurer&#xff09; 前者相信大家都很熟悉&#xff0c;但是实际使用中我们往往想从数据库中读取指定时间来动态…

天眼使用指南--分析平台

#天眼分析平台 提供全面的溯源分析能力&#xff0c;涵盖图中模块。负责存储日志&#xff0c;分为三类&#xff0c;告警日志 告警日志&#xff1a;来自探针和沙箱的告警&#xff0c;探针的告警可以记录双向完整对话&#xff0c;如果网络流量中没有恶意信息&#xff0c;就会储存…

windows Server 2008 R2服务器IIS环境启用TLS 1.2

windows Server 2008 R2服务器IIS环境启用TLS 1.2&#xff0c;配置TLS1.2 分为2步, 添加TLS配置和禁用老的SSL版本&#xff0c;提供两种方法, 选择其中一种就行了&#xff0c;手动设置 打开注册表&#xff0c;运行regedit&#xff0c;找到 HKEY_LOCAL_MACHINE\SYSTEM\CurrentCo…

【hadoop】在linux上设置Hadoop的环境变量

设置Hadoop的环境变量 解压压缩包编辑环境变量激活环境变量 解压压缩包 使用下面命令对hadoop的压缩包进行解压 tar -zxvf hadoop-2.7.3.tar.gz -C ~/training/编辑环境变量 在linux中&#xff0c;~/.bash_profile文件是设置环境变量的文件&#xff0c;我们使用vi进行编辑。…

Verdi之波形展示nWave

6.nWave 6.1 添加波形文件 1.打开nWave界面&#xff0c;具体操作如下&#xff1a; 2.正式添加波形&#xff0c;使用快捷键G或者点击以下图标&#xff0c;选择需要的信号。 也可以在 n Trace中选中信号后&#xff0c;鼠标中键拖拽&#xff0c;或者ctrlw进行添加&#xff1b; 6…

Dreamweaver批量替换所有超链接替换成#

需求&#xff1a;想要将页面所有链接地址替换为#。 方法一 CTRLF打开“查找和替换”&#xff0c;勾选“使用正则表达式” 查找 href"([\s\S]*?)" 替换为 href"#" 副作用&#xff1a;样式表链接地址也会被替换为#&#xff0c;需提前备份。 方法二 也可以查…

CAN总线(二)CAN协议的帧格式(一文看懂CAN的报文结构)

如果只是使用CAN进行CAN通讯,可以粗略看下以下内容,主要了解下数据字段,但了解一下其他内容有助于使用CAN通讯。 一、CAN总线协议规范 CAN报文有两种不同的格式:标准格式和扩展格式,前者的标志符长度是11位,而后者的标志符长度可达29位。 CAN协议的2.0A版本规定CAN控制…

Git -> 创建第一个本地repo

创建一个本地仓库及提交文件 打开Git Bash执行以下命令 // 切换至d盘 cd d: // 新建文件夹 mkdir my_first_local_repo // 切换至新建文件夹 cd my_first_local_repo假设my_first_local_repo文件夹下有以下文件 初始化git仓库 // 在当前文件夹初始化git仓库 git init.gi…

【stable diffusion】保姆级入门课程-Stable diffusion(SD)介绍与安装

目录 0.学前准备 1.什么是AI绘画 2.当前主流的AI绘画工具 3.什么是SD(stable diffusion) 4.SD能做什么 1.文生图 2.图生图 3.AI换模特&#xff0c;背景 5.使用stable diffusion配置要求 6.环境配置与安装 需要注意的地方&#xff1a; 扩展知识&#xff1a; 1.pyth…

Linux学习之环境变量配置文件

配置文件的执行先后顺序如下&#xff1a; /etc/profile $HOME/.bash_profile $HOME/.bashrc /etc/bashrc vim /etc/profile&#xff0c;把echo "/etc/profile"写到第一行&#xff0c;head -n 1 /etc/profile看一下/etc/profile里边第一行内容。 vim $HOME/.bash_pr…

工作:三菱PLC之CC-Link IE Field Network通讯知识及应用

工作&#xff1a;三菱PLC之CC-Link IE Field Network通讯知识及应用 一、理论 1. 简介连接 CC-LINK-IE通讯分别有 CC-Link IE TSN&#xff0c;CC-Link IE Control Network&#xff0c;CC-Link IE Field Network&#xff0c;CC-Link IE Field Network Basic几种形式&#xff…

38译码器

文章目录 38译码器一、38译码器介绍二、项目代码三、仿真代码四、仿真结果 五、总结 38译码器 一、38译码器介绍 38译码器是一种常用的逻辑电路元件&#xff0c;用于将一个3位二进制输入编码转换成8个输出信号之一。它具有多个输入引脚和多个输出引脚。 通常&#xff0c;38译…

Linux下Lua和C++交互

前言 lua&#xff08;wiki 中文 官方社区&#xff1a;lua-users&#xff09;是一门开源、简明、可扩展且高效的弱类型解释型脚本语言。 由于其实现遵循C标准&#xff0c;它几乎能在所有的平台&#xff08;windows、linux、MacOS、Android、iOS、PlayStation、XBox、wii等&…

【Modbus】Modbus协议讲解

Modbus协议讲解 前言一、串口通讯简介二、RS485串口通讯RS485通讯标准的由来&#xff08;了解&#xff09;RS485特点RS-485终端电阻的选择 三、Modbus协议四、Modbus报文范例 前言 本篇是我参加工作培训时&#xff0c;作为记录笔记用的&#xff0c;因此写的方式不会像前面那些系…

Ceph(分布式文件系统)

Ceph(分布式文件系统) 1、存储基础 单机存储设备 ●DAS&#xff08;直接附加存储&#xff0c;是直接接到计算机的主板总线上去的存储&#xff09; IDE、SATA、SCSI、SAS、USB 接口的磁盘 所谓接口就是一种存储设备驱动下的磁盘设备&#xff0c;提供块级别的存储 ●NAS&#xf…

详解RocketMQ使用

目录 1.环境 2.生产者、消费者的模式 3.顺序消息 4.广播消息 5.延迟消息 6.批量消息 7.过滤消息 8.事务消息 本文着重聊的是RocketMQ的编程模型&#xff0c;下载安装和概念可以移步博主的另外两篇博文&#xff1a; RocketMQ基础概念__BugMan的博客-CSDN博客 RocketMQ…

dede编辑器修改成纯文本编辑器的方法

我在做优秀啦网站大全的时候需要的正文内容都不需要设置什么文字样式&#xff0c;所以我需要把编辑器上的工具全部取消掉&#xff0c;包括会员投稿中的编辑器工具栏全部取消掉或者屏蔽隐藏掉&#xff0c;所以我需要把DEDE编辑器修改成纯文本编辑器的方法如下&#xff1a;如图&a…