【C++】动态内存与智能指针——shared_ptr 和 new 结合使用
12.1.3 shared_ptr 和 new 结合使用
如上文所述,如果我们不初始化一个智能指针,那么它将会被初始化为一个空指针(需要注意的是,智能指针与普通指针在此处有着非常明显的区别。如果只声明某个类型的普通指针,而不对它进行初始化,那么这将会是危险的,因为不经初始化的普通指针是空悬指针,它所指向的地址是未知的。而不经初始化的智能指针是一个空指针,它是安全的)。还可以用 new 返回的指针来初始化智能指针:
shared_ptr<double> p1; // shared_ptr 可以指向一个 double
shared_ptr<int> p2(new int(42)); // p2 指向一个值为 42 的 int
接受指针参数的智能指针构造函数是 explicit 的。因此不能将一个内置隐式指针转换为一个智能指针,必须使用直接初始化形式来初始化一个普通指针:
shared_ptr<int> p1 = new int(1024); // 错误❌: 必须使用直接初始化形式
shared_ptr<int> p2(new int(1024)); // 正确👌: 使用了直接初始化形式
p1 的初始化隐式地要求编译器用一个 new 返回的 int* 来创建一个 shared_ptr。由于我们不能进行内置指针到智能指针的隐式转换,因此这条初始化语句是错误的。出于相同的原因,一个返回 shared_ptr 的函数不能在其返回语句中隐式转换一个普通指针:
shared_ptr<int> clone(int p) {return new int(p); // 错误❌: 隐式转换为 shared_ptr<int>
}
(这一部分很好理解,C++ 智能指针的规则就是不能将普通指针隐式地转换为智能指针,由于 new 的返回值是某个类型的普通指针,普通指针不能隐式转为智能指针,因此使用 new … 对智能指针进行赋值是非法的)
我们必须将 shared_ptr 显式绑定到一个想要返回的指针上:
shared_ptr<int> clone(int p) {return shared_ptr<int>(new int(p)); // 正确👌: 显式地使用 int* 来创建 shared_ptr<int>
}
默认情况下,一个用来初始化智能指针的普通指针必须指向动态内存,因为智能指针默认使用 delete 释放它所关联的对象。
可以将智能指针绑定到一个指向其它类型的资源的指针上,但是为了这样做,我们必须提供自己的操作来替代 delete。


不要混合使用普通指针和智能指针 … …
shared_ptr 可以协调对象的析构,但这仅限于其自身的拷贝(也是 shared_ptr)之间。这也是 C++ Primer 推荐使用 make_shared 而不是 new 的原因。
这样,我们就可以在分配对象的同时将 shared_ptr 与之绑定,从而避免了无意中将同一块内存绑定到多个独立创建的 shared_ptr 上。
考虑下面对 shared_ptr 进行操作的函数:
// 在函数调用时 shared_ptr 被创建并初始化
void process(shared_ptr<int> ptr) {// 使用 ptr
} // ptr 离开作用域, 被销毁
process 的参数是传值方式调用的,因此实参会被拷贝到 ptr 中。拷贝一个 shared_ptr 会递增其引用计数,因此,在 process 运行过程中,引用计数值至少为 2。当 process 结束时,ptr 的引用计数递减,但不会变为 0。因此,当局部变量 ptr 被销毁时,ptr 指向的内存不会被释放。
使用此函数的正确方法是传递给它一个 shared_ptr:
shared_ptr<int> p(new int(42)); // 引用计数为 1
process(p); // 拷贝 p 会递增它的引用计数; 在 process 中引用计数值为 2
int i = *p; // 正确: 引用计数值为 1
虽然不能传递给 process 一个内置指针,但可以传递给它一个(临时的)shared_ptr,这个 shared_ptr 是用一个内置指针显式构造的。但这样做可能会导致错误:
int *x(new int(1024)); // 危险: x 是一个普通指针, 不是一个智能指针;
process(x); // 错误❌: 不能将 int* 转换为一个 shared_ptr<int>;
process(shared_ptr<int>(x)); // 合法的, 但内存会被释放;
int j = *x; // 未定义的, x 是一个空悬指针;
在上面的调用中,第一行建立一个指向动态内存的 x 指针,它是一个 int 类型的指针,指向的值为 1024,并且它是一个普通指针。第二行是非法的,因为 shared_ptr 和 普通指针之间没有隐式转换。第三行是正确的,因为第三行的实参中显式地将普通指针转为智能指针,但是智能指针所指向的地址会在函数调用结束时被释放。因此,第四行是危险的,因为 x 所指向的内存已经被释放(被释放的过程在 C++ Primer 当中的解释不是很详尽,我的理解是这样的:当第三行对 process 进行调用时,x 被显式地转换为智能指针,指向 x 所指地址的引用计数被置为 1。当函数调用结束时,x 所指地址的引用计数被置为 0,该部分的内存随之被释放。此时,由于函数调用外的 x 是一个普通指针,它所指向的地址仍然是最初的地址,而最初地址的内存在 process 函数调用后被释放,所以此时 x 指向的地址被释放,x 变为一个危险的空悬指针)。
当将一个 shared_ptr 绑定到一个普通指针时,我们就将内存的管理责任交给了这个 shared_ptr。一旦这样做了,我们就不能再使用内置指针来访问 shared_ptr 所指向的内存了。
使用一个内置指针来访问一个智能指针所负责的对象是很危险的,因为我们无法知道对象何时被销毁。
… … 也不要使用 get 初始化另一个智能指针或为智能指针赋值
智能指针类型定义了一个名为 get 的函数,它返回一个内置指针,指向智能指针关联的对象。此函数是为了这样一种情况而设计的:我们需要向一个不能使用智能指针的代码传递一个内置指针。使用 get 返回的指针的代码不能 delete 此指针。
虽然编译器不会给出错误信息,但将另一个智能指针也绑定到 get 返回的指针上是错误的:
shared_ptr<int> p(new int(42)); // 引用计数为 1
int *q = p.get(); // 正确👌: 但使用 q 时要注意, 不要让它管理的指针被释放
{ // 新程序块shared_ptr<int>(q); // 未定义: 两个独立的 shared_ptr 指向相同的内存
} // 程序块结束, q 被销毁, 它指向的内存被释放
int foo = *p; // 未定义: p 指向的内存已经被释放;
在本例中,p 和 q 指向相同的内存。由于它们是相互独立创建的,因此各自的引用计数都是 1。当 q 所在的程序块结束时,q 被销毁,这会导致 q 指向的内存被释放。从而 p 变成一个空悬指针。
get 用来将指针的访问权限传递给代码,你只有在确定代码不会 delete 指针的情况下,才能使用 get。
其它 shared_ptr 操作
shared_ptr 还定义了一些其它操作。可以用 reset 来将一个新的指针赋予一个 shared_ptr:
p = new int(1024); // 错误❌: 不能将一个指针赋予 shared_ptr
p.reset(new int(1024)); // 正确👌: p 指向一个新对象
与赋值类似,reset 会更新引用计数,如果需要的话,会释放 p 指向的对象。reset 成员常与 unique 一起使用,来控制多个 shared_ptr 共享的对象。
相关文章:
【C++】动态内存与智能指针——shared_ptr 和 new 结合使用
12.1.3 shared_ptr 和 new 结合使用 如上文所述,如果我们不初始化一个智能指针,那么它将会被初始化为一个空指针(需要注意的是,智能指针与普通指针在此处有着非常明显的区别。如果只声明某个类型的普通指针,而不对它进…...
遥感数据集:FTW全球农田边界和对应影像数据,约160万田块边界及7万多个样本
Fields of The World (FTW) 是一个面向农业田地边界实例分割的基准数据集,旨在推动机器学习模型的发展,满足全球农业监测对高精度、可扩展的田地边界数据的需求。该数据集由kerner-lab提供,于2024年8月28日发布,主要特征包括&…...
马斯克的 AI 游戏工作室:人工智能与游戏产业的融合新纪元
近日,马斯克在 X 平台(前身为 Twitter)发文称,“太多游戏工作室被大型企业所拥有,xAI 将启动一个 AI 游戏工作室,让游戏再次变得精彩”。这一言论不仅展示了马斯克对游戏行业现状的不满,也揭示了…...
URDF(描述机器人模型)和SDF(Gazebo中用于描述仿真环境)
使用URDF(Unified Robot Description Format) URDF是ROS中用于描述机器人模型的XML格式文件。你可以使用XML文件定义机器人的几何形状、惯性参数、关节和链接等。 示例URDF文件(my_robot.urdf): <?xml version&…...
力扣380:O(1)时间插入、删除和获取随机数
实现RandomizedSet 类: RandomizedSet() 初始化 RandomizedSet 对象bool insert(int val) 当元素 val 不存在时,向集合中插入该项,并返回 true ;否则,返回 false 。bool remove(int val) 当元素 val 存在时࿰…...
【C++boost::asio网络编程】有关socket的创建和连接的笔记
socket的创建和连接 tcp客户端创建端点tcp服务端创建端点创建socket创建TCP 服务器端的 acceptor 套接字创建 acceptor 套接字并绑定客户端连接到服务器通过ip地址解析通过域名解析 服务端接收新连接 tcp客户端创建端点 int client_end_point() {std::string raw_ip_address …...
超级灵感:前端页面功能统一管理方案
前端页面功能统一管理方案 引言 我和朋友聊天想到一个灵感,关于支付状态机管理,这个类可以让我们知道具体上一个状态和下一个状态,这是由于那个事件触发改变,这个功能设计非常好! 从而讨论出为什么我们不能把某一个…...
力扣第 77 题 组合
题目描述 给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按任意顺序返回答案。 示例 示例 1 输入: n 4, k 2输出: [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]示例 2 输入: n 1, k …...
(超详细图文)PLSQL Developer 配置连接远程 Oracle 服务
1、下载配置文件 (超详细图文详情)Navicat 配置连接 Oracle-CSDN博客 将下载的文件解压到单独文件夹,如:D:\App\App_Java\Oracle\instantclient-basic-windows.x64-19.25.0.0.0dbru 2、配置 打开 PLSQL Developer,登…...
元器件选型与参数13 电源的分类-线性电源参数 RT9013 AMS1117 PCB布局布线
目录 一、线性电源 1、重要参数 2、线性电源效率一定低吗 3、线性电源并联扩流 4、常见电路 RT9013-LDO AMS1117-xx-LDO 5、布局布线 6、外置输入与电池供电 7、单片机控制其他模组供电实现低功耗 二、开关电源与线性电源配合 1、高效率与低噪声 DC-DC电源大致分为…...
RHEL7+Oracle11.2 RAC集群-多路径(multipath+udev)安装步骤
RHEL7Oracle11.2RAC集群-多路径(multipathudev)安装 配置虚拟存储 使用StarWind Management Console软件,配置存储 dggrid1: 1g*3 Dggrid2: 1g*3 Dgsystem: 5g*1 系统表空间,临时表空间,UNDO,参数文件…...
每日速记10道java面试题03
其他资料 每日速记10道java面试题01-CSDN博客 每日速记10道java面试题02-CSDN博客 目录 一、你使用过java的反射机制吗?如何应用反射? 二、什么是泛型?泛型的作用是什么? 三、java的泛型擦除是什么? 四、Java 中…...
Vue 3 的双向绑定原理
Vue 3 的双向绑定原理是基于 响应式系统 和 数据劫持 技术来实现的。在 Vue 3 中,双向绑定通常是通过 v-model 指令来完成的,它本质上是数据的双向同步:当数据改变时,视图自动更新,反之,视图的修改也会更新…...
如何使用 Chrome 无痕浏览模式访问网站?
无痕浏览(Incognito Mode)是 Google Chrome 浏览器提供的一种隐私保护功能,它允许用户在一个独立的会话中浏览网页,而不会记录用户的浏览历史、下载历史、表单数据等。这对于希望保护个人隐私或进行临时性匿名浏览的用户来说非常有…...
Idea 2024.3 突然出现点击run 运行没有反应,且没有任何提示。
写这篇文章的目的是为了提供一个新的解决思路,因为存在同病不同原因。 如果你进行了1. 检查运行配置 (Run Configuration) 2. 清理和重建项目 3. 清除缓存并重启 IDEA 4.排除kotlin 5.重装idea等等操作之后仍然没有解决,可以试着按一下步骤进行解决。 检…...
【小白学机器学习36】关于独立概率,联合概率,交叉概率,交叉概率和,总概率等 概念辨析的例子
目录 1 先说结论 2 联合概率 3 边缘概率 4 (行/列)边缘概率的和 总概率1 5 条件概率 5.1 条件概率的除法公式 5.2 条件概率和联合概率区别 1 先说结论 关于独立概率,联合概率,交叉概率,交叉概率和,总概率 类型含义 …...
Spring Boot 项目——分层架构
在创建一个 Spring Boot 项目时,为了提高代码的可维护性、可扩展性和清晰度,通常会按照一定的分层架构进行设计。常见的分层架构包括以下几层: 1. Controller 层(Web 层) 作用:接收用户请求,并…...
wordpress网站首页底部栏显示网站备案信息
一、页脚文件footer.php 例如,wordpress主题使用的是simple-life主题,服务器IP为192.168.68.89,在wordpress主题文件中有个页脚文件footer.php,这是一个包含网站页脚代码的文件。 footer.php 路径如下: /www/wwwroot/192.168.68…...
python面向对象编程练习
学生成绩管理系统 定义一个Student类,包括属性(姓名、成绩)和方法(设置成绩、获取成绩、计算平均成绩)。 实例化多个学生对象并调用方法。 功能说明: Student 类: init(self, name):…...
OpenCV_Code_LOG
孔洞填充 void fillHole(const Mat srcBw, Mat &dstBw) {Size m_Size srcBw.size();Mat TempMat::zeros(m_Size.height2,m_Size.width2,srcBw.type());//延展图像srcBw.copyTo(Temp(Range(1, m_Size.height 1), Range(1, m_Size.width 1)));cv::floodFill(Temp, Point(…...
OpenLayers 可视化之热力图
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 热力图(Heatmap)又叫热点图,是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...
3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...
HashMap中的put方法执行流程(流程图)
1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中,其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下: 初始判断与哈希计算: 首先,putVal 方法会检查当前的 table(也就…...
LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...
