C++ 11/14/17 智能指针
1. 简介
为了更加容易(更加安全)的使用动态内存,引入了智能指针的概念。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。
标准库提供的两种智能指针的区别在于管理底层指针的方法不同:shared_ptr和unique_ptr。
1)shared_ptr允许多个指针指向同一个对象;
2)unique_ptr则“独占”所指向的对象。
标准库还定义了一种名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象,这三种智能指针都定义在memory头文件中。
2. shared_ptr
多个shared_ptr指向同一处资源,当所有shared_ptr都全部释放时,该处资源才释放。
1. 内部大概实现:
每次复制,多一个共享同处资源的shared_ptr时,计数+1。每次释放shared_ptr时,计数-1。
当shared计数为0时,则证明所有指向同一处资源的shared_ptr们全都释放了。
//shared计数放在这个结构体里面,实际上结构体里还应该有另一个weak计数。下文介绍weak_ptr时会解释。
struct SharedPtrControlBlock
{int shared_count;
};
//大概长这个样子(简化版)
template<class T>
class shared_ptr
{T* ptr;SharedPtrControlBlock* count;
};
2. 示例用法:
void runGame()
{std::shared_ptr<Monster> monster1(new Monster()); //计数加到1
do{std::shared_ptr<Monster> monster2 = monster1; //计数加到2}while(0);
//该栈退出后,计数减为1,monster1指向的堆对象仍存在
std::shared_ptr<Monster> monster3 = monster1; //计数加到2
}//该栈退出后,shared_ptr都释放了,计数减为0,它们指向的堆对象也能跟着释放.
缺陷:模型循环依赖(互相引用或环引用)时,计数会不正常
创建智能指针时必须提供额外的信息,指针可以指向的类型:
shared_ptr<string>p1;
shared_ptr<list<int>> p2;
默认初始化的智能指针中保存着一个空指针。
智能指针的使用方式和普通指针类似,解引用一个智能指针返回它指向的对象,在一个条件判断中使用智能指针就是检测它是不是空。
if(p1 && p1->empty())*p1 = "hi";
3. 如下表所示是shared_ptr特有的操作:

函数说明
1)make_share函数
最安全的分配和使用动态内存的方法就是调用一个名为make_shared的标准库函数,此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。头文件和share_ptr相同,在memory中 。
必须指定想要创建对象的类型,定义格式如下:
shared_ptr<int> p3 = make_shared<int>(42);
shared_ptr<string> p4 = make_shared<string>(10,'9');
shared_ptr<int> p5 = make_shared<int>();
make_shared用其参数来构造给定类型的对象,如果我们不传递任何参数,对象就会进行值初始化。
2)shared_ptr的拷贝和赋值
当进行拷贝和赋值时,每个shared_ptr都会记录1有多少个其他shared_ptr都会记录有多少个其他shared_ptr指向相同的对象。
auto p = make_shared<int>(42);
auto q(p);
我们可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数,无论何时我们拷贝一个shared_ptr,计数器都会递增。当我们给shared_ptr赋予一个新值或是shared_ptr被销毁时,计数器就会递减,一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。
auto r = make_shared<int>(42); //r指向的int只有一个引用者r=q; //给r赋值,令它指向另一个地址//递增q指向的对象的引用计数//递减r原来指向的对象的引用计数//r原来指向的对象已没有引用者,会自动释放
3. weak_ptr
weak_ptr是为了辅助shared_ptr的存在,它只提供了对管理对象的一个访问手段,同时也可以实时动态地知道指向的对象是否存活。
1. 内部大概实现
1)计数区域(SharedPtrControlBlock)结构体引进新的int变量weak_count,来作为弱引用计数。
2)每个weak_ptr都占指针的两倍空间,一个装着原始指针,一个装着计数区域的指针(和shared_ptr一样的成员)。
3)weak_ptr可以由一个shared_ptr或者另一个weak_ptr构造。
4)weak_ptr的构造和析构不会引起shared_count的增加或减少,只会引起weak_count的增加或减少。
被管理资源的释放只取决于shared计数,当shared计数为0,才会释放被管理资源,也就是说weak_ptr不控制资源的生命周期。
但是计数区域的释放却取决于shared计数和weak计数,当两者均为0时,才会释放计数区域。
//shared引用计数和weak引用计数
//之前的计数区域实际最终应该长这个样子
struct SharedPtrControlBlock
{int shared_count;int weak_count;
};
//大概长这个样子(简化版)
template<class T>
class weak_ptr
{T* ptr;SharedPtrControlBlock* count;
};
2. 针对空悬指针问题
空悬指针问题是指:无法知道指针指向的堆内存是否已经释放。
得益于引入的weak_count,weak_ptr指针可以使计数区域的生命周期受weak_ptr控制,
从而能使weak_ptr获取 被管理资源的shared计数,从而判断被管理对象是否已被释放。(可以实时动态地知道指向的对象是否被释放,从而有效解决空悬指针问题)
它的成员函数**expired()**就是判断指向的对象是否存活。
3. 针对循环引用问题
class Monster{
//尽管父子可以互相访问,但是彼此都是独立的个体,无论是谁都不应该拥有另一个人的所有权。
std::weak_ptr<Monster> m_father; //所以都把shared_ptr换成了weak_ptr
std::weak_ptr<Monster> m_son; //同上public:
void setFather(std::shared_ptr<Monster>& father); //实现细节懒得写了
void setSon(std::shared_ptr<Monster>& son); //懒
~Monster(){std::cout << "A monster die!";} //析构时发出死亡的悲鸣
};
然后执行下面的函数:
void runGame()
{std::shared_ptr<Monster> father(new Monster());std::shared_ptr<Monster> son(new Monster());father->setSon(son);son->setFather(father);
}
那么我们再来模拟一遍,函数退出时栈的shared_ptr对象陆续释放后的情形:
一开始:father指向的堆对象 shared计数为1,weak计数为1;son指向的堆对象 shared计数为1,weak计数为1;
son智能指针退出栈:son指向的堆对象 shared计数减为0,weak计数为1,释放son的堆对象;father指向的堆对象 shared计数为1,weak计数减为0;
father智能指针退出栈:father指向的堆对象 shared计数减为0,weak计数为0;释放father的堆对象和father的计数区域;son指向的堆对象 shared计数为0,weak计数减为0;释放son的计数区域。
函数结束,释放行为正确。
(可以说,当生命控制权没有彼此互相掌握时,才能正确解决循环引用问题,而弱引用的使用可以使生命控制权互相掌握的情况消失)
此外:
weak_ptr没有重载 * 和 -> ,所以并不能直接使用资源。但可以使用lock()获得一个可用的shared_ptr对象,如果对象已经死了,lock()会失败,返回一个空的shared_ptr。
void runGame()
{std::shared_ptr<Monster> monster1(new Monster());std::weak_ptr<Monster> r_monster1 = monster1;r_monster1->doSomething();//Error! 编译器出错!weak_ptr没有重载* 和 -> ,无法直接当指针用 std::shared_ptr<Monster> s_monster1 = r_monster1.lock();//OK!可以通过weak_ptr的lock方法获得shared_ptr。
}

4. 小结
推荐用法:一个shared_ptr和n个weak_ptr搭配使用 而不是n个shared_ptr。
逻辑上,大部分模型的生命在直观上总是受某一样东西直接控制而不是多样东西共同控制。
程序上,能够完全避免生命周期互相控制引发的 循环引用问题。
相关文章:
C++ 11/14/17 智能指针
1. 简介 为了更加容易(更加安全)的使用动态内存,引入了智能指针的概念。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。 标准库提供的两种智能指针的区别在于管理底层指针的方法不同:shared_p…...
C++入门【37-C++ 拷贝构造函数】
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于: 通过使用另一个同类型的对象来初始化新创建的对象。复制对象把它作为参数传递给函数。复制对象,并…...
[UI5 常用控件] 06.Splitter,ResponsiveSplitter
文章目录 前言1. Splitter1.1 属性 2. ResponsiveSplitter 前言 本章节记录常用控件Splitter,ResponsiveSplitter。主要功能是分割画面布局。 其路径分别是: sap.ui.layout.Splittersap.ui.layout.ResponsiveSplitter 1. Splitter 1.1 属性 orientation &#x…...
C遗漏知识(个人向)
之前C语言遗漏的一些。 数据在内存中的存储 原码、反码、补码 整数的2进制表⽰⽅法有三种,即 原码、反码和补码 正整数的原、反、补码都相同。 负整数的三种表⽰⽅法各不相同。 原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。 反码&…...
ERR_SSL_VERSION_OR_CIPHER_MISMATCH
我在namesilo买的域名,coludflare做的解析,华为云的SSL,用宝塔部署的SSL,访问https报错,http却正常: 报错:此网站无法提供安全连接www.hongkong.ioyunxin.top 使用了不受支持的协议。 ERR_SSL_…...
2.5作业
一 、程序阅读题 1、给出下面程序输出结果。 #include <iostream.h> class example {int a; public: example(int b5){ab;} void print(){aa1;cout <<a<<"";} void print()const {cout<<a<<endl;} }; void main() {example x; const e…...
linux系统lvs命令的使用
Lvs命令 LVS ipvsadm 命令的使用LVS-server安装lvs管理软件命令选项 LVS ipvsadm 命令的使用 LVS-server安装lvs管理软件 yum -y install ipvsadm 程序包:ipvsadm(LVS管理工具) 主程序:/usr/sbin/ipvsadm 规则保存工具&#x…...
PoEAA笔记-7.分布策略
本文摘抄自PoEAA,详细信息请阅读本书 7.1 分布对象的诱惑 透明性非常有用,但虽然有很多东西在分布对象中可以是透明的,但性能却不在其中,尽管上面的架构师是为了提高性能而使用分布组件的,但他的设计只会影响性能&…...
Spring Boot 整合 Redis 使用教程
作为开发者,相信大家都知道 Redis 的重要性。Redis 是使用 C 语言开发的一个高性能键值对数据库,是互联网技术领域使用最为广泛的存储中间件,它是「Remote Dictionary Service」的首字母缩写,也就是「远程字典服务」。 Redis 以超…...
用友U8 Cloud ReportDetailDataQuery SQL注入漏洞复现(QVD-2023-47860)
0x01 产品简介 用友U8 Cloud 提供企业级云ERP整体解决方案,全面支持多组织业务协同,实现企业互联网资源连接。 U8 Cloud 亦是亚太地区成长型企业最广泛采用的云解决方案。 0x02 漏洞概述 用友U8 cloud ReportDetailDataQuery 接口处存在SQL注入漏洞,攻击者未经授权可以访…...
docker镜像命令
docker images 列表本机上的镜像 - REPOSITORY:表示镜像的仓库源 - TAG:镜像的标签 - IMAGE ID:镜像 - ID CREATED:镜像创建时间 - SIZE:镜像大小 同一仓库源可以有多个 TAG,代表这个仓库源的不同个版本&am…...
通义千问上线春节新应用,AI帮你免费拍全家福
2月5日,春节将至年味渐浓,阿里云通义千问APP上线多项免费新应用,涵盖全家福、拜新年、万物成龙等图像生成的新玩法,共提供超300套照片模板,用户上传照片即可生成全家福、团圆照、拜年照、千里江山主题照;此…...
RabbitMQ 安装
下载erlang语言: erlang语言 下载RabbitMQ rabbitmq 安装erlang 1.以管理员身份安装erlang 2.弹出框选择next 3.选择安装路径,亦可以安装在默认路径 4.接下来一路点击下一步,无需任何修改,直到 install安装为止ÿ…...
如何让MySQL从部署到稳定运行?
如何让MySQL从部署到稳定运行? 1. 安装MySQL 8保姆级教程 2. 《从菜鸟到大师之路 MySQL 篇》 3. 关于MySQL的66个问题 4. MySQL 的学习资源史上最全 5. 掌握 SQL 这些核心知识点,出去吹牛逼再也不担心了...
go 内存二进制数据操作
go 内存二进制数据操作 go 内存二进制数据直接操作 以数字类型为例 int(linux/macos 为int32,windows 为int64). 如果不清楚可以使用unsafe.Sizeof函数来查看(函数出来的值*8就是int位数) 若不使用内存二进制数据操作,你需要在每次获取数字内容时调用binary.Big…...
Antd+React+react-resizable实现表格拖拽功能
1、先看效果 2、环境准备 在package.json 引入相关的依赖 "dependencies": {"antd": "^5.4.0","react-resizable": "^3.0.4",},"devDependencies": {"types/react": "^18.0.33","types…...
StringBuilder类常用方法(Java)
StringBuilder类常用方法 StringBuilder 是 Java 中常用的字符串缓冲区类,适用于频繁修改字符串的场景。 1. append(): 将指定字符串、字符、布尔值或其他数据类型的表示追加到字符串缓冲区的末尾。 StringBuilder sb new StringBuilder("Hello"); sb.…...
Iceberg从入门到精通系列之二十一:Spark集成Iceberg
Iceberg从入门到精通系列之二十一:Spark集成Iceberg 一、在 Spark 3 中使用 Iceberg二、添加目录三、创建表四、写五、读六、Catalogs七、目录配置八、使用目录九、替换会话目录十、使用目录特定的 Hadoop 配置值十一、加载自定义目录十二、SQL 扩展十三、运行时配置…...
Linux的进程信号
注意:首先需要提醒一个事情,本节提及的进程信号和下节的信号量没有任何关系,请您区分对待。 1.信号概念 1.1.生活中的信号 我们在生活中通过体验现实,记忆了一些信号和对应的处理动作,这意味着信号有以下相关的特点&…...
svn常用命令及过滤文件 global ignore pattern
SVN常用命令详解和global ignore pattern Subversion(SVN)是一个版本控制系统,广泛用于软件开发项目中。它能够追踪文件的变更,并且允许多人在同一个项目中协同工作。以下是一些常用的SVN命令及其用法。 1. 检出代码 要从SVN服…...
Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...
【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
Windows安装Miniconda
一、下载 https://www.anaconda.com/download/success 二、安装 三、配置镜像源 Anaconda/Miniconda pip 配置清华镜像源_anaconda配置清华源-CSDN博客 四、常用操作命令 Anaconda/Miniconda 基本操作命令_miniconda创建环境命令-CSDN博客...
第7篇:中间件全链路监控与 SQL 性能分析实践
7.1 章节导读 在构建数据库中间件的过程中,可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中,必须做到: 🔍 追踪每一条 SQL 的生命周期(从入口到数据库执行)&#…...
嵌入式常见 CPU 架构
架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集,单周期执行;低功耗、CIP 独立外设;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel(原始…...
git: early EOF
macOS报错: Initialized empty Git repository in /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/.git/ remote: Enumerating objects: 2691797, done. remote: Counting objects: 100% (1760/1760), done. remote: Compressing objects: 100% (636/636…...
ubuntu22.04 安装docker 和docker-compose
首先你要确保没有docker环境或者使用命令删掉docker sudo apt-get remove docker docker-engine docker.io containerd runc安装docker 更新软件环境 sudo apt update sudo apt upgrade下载docker依赖和GPG 密钥 # 依赖 apt-get install ca-certificates curl gnupg lsb-rel…...
