C++(11): 智能指针shared_ptr

1. 概述

        shared_ptr智能指针,本质是“离开作用域会自动调整(减小)引用计数,如果引用计数为0,则会调用析构函数”。这样一来,就进化成类似于int、float等的一种会被自动释放的类型。

2. 初始化智能指针

        初始化一个智能指针的方式比较多,可以构造一个空的智能指针,也可以通过调用new进行初始化,最推荐的还是通过make_shared<T>来构造。

        如下是几种构造方式。

(1)构造空的智能指针

        shared_ptr<T> ptr;

        就相当于一个 NULL 指针

(2)调用new

        从new操作符的返回值构造

        shared_ptr<T> ptr(new T());

(3)拷贝构造

        shared_ptr<T> ptr2(ptr1);

        使用拷贝构造函数的方法,会让引用计数加 1。

        shared_ptr 可以当作函数的参数传递,或者当作函数的返回值返回,这个时候其实也相当于使用拷贝构造函数。

(4)使用make_shared构造

        比较推荐使用make_shared辅助创建

        std::shared_ptr<T>foo = std::make_shared<T>(10); 

        此处仅以构造时形参为一个int为例,实际情况可变。

3. 具有继承关系的智能指针转换

        当两个类具有继承关系时,我们需要使用dynamic_pointer_cast或static_pointer_cast进行一个转换。

        在介绍两个API之前,我们先定义两种转换形式。假设我们有两个类,分别是Base类和Derived类,其中Derived类继承自Base类。

        下行转换:Base类的指针指向Drived类;

        下行转换:Drived类的指针指向Base类;

(1)dynamic_pointer_cast

        当我们一般进行下行转换时,一般使用dynamic_pointer_cast。这样在不确定下行转换是否可行时,可以进行对象实际类型的检查,如果不能够转换,则返回NULL指针。

/** 假设B是A的子类 */shared_ptr<B> ptrb(new B());shared_ptr<A> ptra(dynamic_pointer_cast<A>(ptrb) ); ///< 从shared_ptr提供的类型转换(dynamic_pointer_cast)函数的返回值构造

(2)static_pointer_cast

        既可以用在上行转换,又可以用在下行转换。

        需要注意的是,用于下行转换时,并不会进行类型的检查,如果不能够转换,会发生未定义的行为。因此,使用static_pointer_cast的前提是,开发者需要确切的指导下行转换是都是什么样的类型,开发者需要了解能不能转换。

4. 智能指针赋值

        如下程序所示,在赋值以后a原先所指的对象会被销毁,b所指的对象引用计数加1。

        shared_ptr可以直接赋值,但是必须是赋给相同类型的shared_ptr对象,而不能是普通的C指针或new运算符的返回值。

        当共享指针a被赋值成b的时候,如果a原来是NULL, 那么直接让a等于b并且让它们指向的东西的引用计数加1;

        如果a原来也指向某些东西的时候,如果a被赋值成b, 那么原来a指向的东西的引用计数被减1, 而新指向的对象的引用计数加1。

/** shared_ptr 的“赋值” */shared_ptr<T> a(new T());shared_ptr<T> b(new T());a = b;  

5. 智能指针重置

        我们在使用智能指针过程中,偶尔会重置,将智能指针指向其他对象,这个时候可以使用reset成员。

/** 已定义的共享指针指向新的new对象: reset() */shared_ptr<T> ptr(new T());ptr.reset(new T());

如上操作,原来所指的对象会被销毁。

6. 常用函数

        get(): 获取源类型指针;

        use_count();获取引用计数;

        reset():重置指针为NULL;

7. shared_ptr存在的问题

        可以说shared_ptr是一种非常实用化的应用形式,但也存在一些问题。如下:

(1)内存无法及时释放

        只有当循环计数为0时,才会释放内存;

(2)循环引用

        当出现循环引用时,易出现内存泄漏,可利用weak_ptr解决;

8. 优缺点

        讲完了如何使用,接下来我们讲一下优缺点。

优点

(1)效率更高

        shared_ptr 需要维护引用计数的信息,

        强引用, 用来记录当前有多少个存活的 shared_ptrs 正持有该对象. 共享的对象会在最后一个强引用离开的时候销毁( 也可能释放).

        弱引用, 用来记录当前有多少个正在观察该对象的 weak_ptrs. 当最后一个弱引用离开的时候, 共享的内部信息控制块会被销毁和释放 (共享的对象也会被释放, 如果还没有释放的话).

(2)分配方式灵活

        如果你通过使用原始的 new 表达式分配对象, 然后传递给 shared_ptr (也就是使用 shared_ptr 的构造函数) 的话, shared_ptr 的实现没有办法选择, 而只能单独的分配控制块:

        auto p = new widget();

        shared_ptr sp1{ p }, sp2{ sp1 };

如果选择使用 make_shared 的话, 情况就会变成下面这样:

auto sp1 = make_shared(), sp2{ sp1 };

        内存分配的动作, 可以一次性完成. 这减少了内存分配的次数, 而内存分配是代价很高的操作.

关于两种方式的性能测试可以看这里 Experimenting with C++ std::make_shared

(3)异常安全

        看看下面的代码:

void Func(const std::shared_ptr<Lhs>& lhs, const std::shared_ptr<Rhs>& rhs){/* ... */}/** 调用函数,导入两个智能指针. */Func(std::shared_ptr<Lhs>(new Lhs("foo")), std::shared_ptr<Rhs>(new Rhs("bar")));

        C++ 不能保证参数求值顺序, 以及内部表达式的求值顺序的, 所以可能的执行顺序如下:

new Lhs(“foo”))

new Rhs(“bar”))

std::shared_ptr

std::shared_ptr

        此时如果在第 2 步的时候, 抛出了一个异常, 那么第一步申请的 Lhs 对象内存泄露了. 这个问题的核心在于, shared_ptr 没有立即获得裸指针.

        我们可以用如下方式来修复这个问题.

auto lhs = std::make_shared<Lhs>("foo");auto rhs = std::make_shared<Rhs>("bar");Func(lhs, rhs);

缺点

(1)构造函数是保护或私有时,无法使用 make_shared

        make_shared 虽好, 但也存在一些问题, 比如, 当我想要创建的对象没有公有的构造函数时, make_shared 就无法使用了, 当然我们可以使用一些小技巧来解决这个问题, 比如这里 How do I call ::std::make_shared on a class with only protected or private constructors?

(2)对象的内存可能无法及时回收

        make_shared 只分配一次内存, 这看起来很好. 减少了内存分配的开销. 问题来了, weak_ptr 会保持控制块(强引用, 以及弱引用的信息)的生命周期, 而因此连带着保持了对象分配的内存, 只有最后一个 weak_ptr 离开作用域时, 内存才会被释放. 原本强引用减为 0 时就可以释放的内存, 现在变为了强引用, 若引用都减为 0 时才能释放, 意外的延迟了内存释放的时间. 这对于内存要求高的场景来说, 是一个需要注意的问题. 关于这个问题可以看这里 make_shared, almost a silver bullet

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

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

相关文章

深入理解ThreadLocal原理

目录 1- 什么是ThreadLocal &#xff1f;2- ThreadLocal的作用&#xff1f;ThreadLocal实现线程间资源隔离ThreadLocal实现线程内资源共享 3- ThreadLocal 原理3-1 ThreadLocalMap3-2 ThreadLocalMap的扩容&#x1f511;1. 为什么会发生扩容&#xff1f;&#x1f511;2. Thread…

将图像转换为ASCII艺术形式

将图像转换为ASCII艺术形式 在本文中&#xff0c;我们将介绍一个使用OpenCV库将图像转换为ASCII艺术形式的简单程序。ASCII艺术是一种使用字符来表现图像的艺术形式&#xff0c;通过在终端或文本文件中显示字符的不同密度和颜色来模拟图像。这种技术已经存在了几十年&#xff…

【MySQL】7.MHA高可用配置及故障切换

什么是MHA MHA&#xff08;MasterHigh Availability&#xff09;是一套优秀的MySQL高可用环境下故障切换和主从复制的软件 mha用于解决mysql的单点故障问题&#xff1b; 出现故障时&#xff0c;mha能在0~30秒内自动完成故障切换&#xff1b; 并且能在故障切换过程中&#xff0…

史上最强 PyTorch 2.2 GPU 版最新安装教程

一 深度学习主机 1.1 配置 先附上电脑配置图&#xff0c;如下&#xff1a; 利用公司的办公电脑对配置进行升级改造完成。除了显卡和电源&#xff0c;其他硬件都是公司电脑原装。 1.2 显卡 有钱直接上 RTX4090&#xff0c;也不能复用公司的电脑&#xff0c;其他配置跟不上。…

ARM FVP平台的terminal窗口大小如何设置

当启动ARM FVP平台时&#xff0c;terminal窗口太小怎么办&#xff1f;看起来非常累眼睛&#xff0c;本博客来解决这个问题。 首先看下ARM FVP平台对Host主机的需求&#xff1a; 通过上图可知&#xff0c;UART默认使用的是xterm。因此&#xff0c;我们需要修改xterm的默认字体设…

C++语言学习(一)——关键字、命名空间、输入输出

1. C关键字 C总计63个关键字&#xff0c;C语言32个关键字 2. 命名空间 在C/C中&#xff0c;变量、函数和后面要学到的类都是大量存在的&#xff0c;这些变量、函数和类的名称将都存在于全局作用域中&#xff0c;可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本…

yolov5关键点检测-实现溺水检测与警报提示(代码+原理)

基于YOLOv5的关键点检测应用于溺水检测与警报提示是一种结合深度学习与计算机视觉技术的安全监控解决方案。该项目通常会利用YOLOv5强大的实时目标检测能力&#xff0c;并通过扩展或修改网络结构以支持人体关键点检测&#xff0c;来识别游泳池或其他水域中人们的行为姿态。 项…

Java入门学习Day04

本篇文章主要介绍了&#xff1a;如何输入数据、字符串拼接、自增自减运算符、类型转换&#xff08;int&#xff0c;double等&#xff09; CSDN&#xff1a;码银 公众号&#xff1a;码银学编程 一、键盘输入练习 Scanner是Java中的一个类&#xff0c;用于从控制台或文件中读…

DOTS:Burst

目录 一&#xff1a;简介 1.1 Getting started 1.2 C# language support 1.2.1 HPC# overview 1.2.1.1 Exception expressions 1.2.1.2 Foreach and While 1.2.1.3 Unsupported C# features in HPC# 1.2.2 Static read-only fields and static constructor support 1.…

STM32-03基于HAL库(CubeMX+MDK+Proteus)输入检测案例(按键控制LED)

文章目录 一、功能需求分析二、Proteus绘制电路原理图三、STMCubeMX 配置引脚及模式&#xff0c;生成代码四、MDK打开生成项目&#xff0c;编写HAL库的按键检测代码五、运行仿真程序&#xff0c;调试代码 一、功能需求分析 搭建完成开发STM32开发环境之后&#xff0c;开始GPIO…

LC 110.平衡二叉树

110. 平衡二叉树 给定一个二叉树&#xff0c;判断它是否是高度平衡的二叉树。 本题中&#xff0c;一棵高度平衡二叉树定义为&#xff1a; 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。 示例 1&#xff1a; 输入&#xff1a; root [3,9,20,null,null,15,7]…

补充知识

补充知识1 内存的本质是对数据的临时存储 内存与磁盘进行交互时&#xff0c; 最小单位是4kb叫做页框(内存)和页帧(磁盘) 也就是&#xff0c; 如果我们要将磁盘的内容加载到内存中&#xff0c; 可是文件大小只有1kb&#xff0c; 我们也要拿出4kb来存他&#xff0c; 多余的就直…

基于Leaflet.js和Turf.js的等值线区间自定义及颜色自适应实践

目录 前言 一、Turf.js等值线相关制作 1、生成方法 2、主要参数 二、实际案例开发 1、新建展示页面 2、等值线生成 3、基于Leaflet的再优化 总结 前言 在气象方面的GIS应用当中&#xff0c;会根据实际的工作需要建立不同的监测站点。气象监测站的主要功能包括&#xff1…

pnpm--安装与使用

原文网址&#xff1a;pnpm--安装与使用-CSDN博客 简介 本文介绍pnpm的安装与使用。 pnpm由npm/yarn衍生而来&#xff0c;解决了npm/yarn内部潜在的bug&#xff0c;极大的优化了性能&#xff0c;扩展了使用场景&#xff0c;被誉为“最先进的包管理工具”&#xff0c;速度快、…

变量重名情况

变量重名 变量的使用规则&#xff1a;就近原则 第一种情况&#xff1a;局部变量和成员变量重名&#xff0c;使用this关键字访问成员变量 第二种情况&#xff1a;子类成员变量和父类成员变量重名&#xff0c;使用super关键字访问父类成员变量 // 父类 public class Fu {int …

舞蹈网站制作分享,舞蹈培训商城网站设计案例分享,wordpress主题分享

嘿&#xff0c;朋友们&#xff01;今天我要跟你们唠一唠一个超级酷炫的舞蹈培训商城网站设计案例。 咱先说说这个网站的目标哈&#xff0c;那就是得让喜欢舞蹈的小伙伴们能够轻轻松松找到自己心水的课程和商品。 那制作过程都有啥呢&#xff1f;别急&#xff0c;听我慢慢道来。…

C#常见Winform窗体效果

目录 1&#xff0c;窗体闪烁。 2&#xff0c;透明非矩形的窗体。 3&#xff0c;窗口显示&#xff0c;退出呈现平滑效果。 4&#xff0c;窗体不在任务栏中显示&#xff1a; 1&#xff0c;窗体闪烁。 /// <summary>/// 窗体闪烁/// </summary>/// <param na…

在c# 7.3中不可用,请使用9.0或更高的语言版本

参考连接&#xff1a;在c# 7.3中不可用,请使用8.0或更高的语言版本_功能“可为 null 的引用类型”在 c# 7.3 中不可用。请使用 8.0 或更高的语言版本-CSDN博客https://blog.csdn.net/liangyely/article/details/106163660 [踩坑记录] 某功能在C#7.3中不可用,请使用 8.0 或更高的…

STM32 | 通用同步/异步串行接收/发送器USART带蓝牙(第六天原理解析)

STM32 第六天 一、 USART 1、USART概念 USART:(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步串行接收/发送器 USART是一个全双工通用同步/异步串行收发模块,该接口是一个高度灵活的串行通信设备 处理器与外部设备通信的两种方式: u并行通信(…

摸鱼123

摸鱼https://toyaml.com/windowsupdate.html