C++(13): 智能指针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/803368.shtml

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

相关文章

1.Spring的核心思想 —— IOC和DI

1. Spring是什么&#xff1f; 简单的说&#xff0c;Spring其实指的是Spring Framework&#xff08;Spring框架&#xff09;&#xff0c;是一个开源框架。 如果要用一句话概括&#xff1a;它是包含众多工具方法的IOC&#xff08;Inverse of Control控制反转&#xff09;容器。…

【御控物联】JavaScript JSON结构转换(18):数组To对象——多层属性重组

文章目录 一、JSON结构转换是什么&#xff1f;二、案例之《JSON数组 To JSON对象》三、代码实现四、在线转换工具五、技术资料 一、JSON结构转换是什么&#xff1f; JSON结构转换指的是将一个JSON对象或JSON数组按照一定规则进行重组、筛选、映射或转换&#xff0c;生成新的JS…

Golang 开发实战day07 - Functions

&#x1f3c6;个人专栏 &#x1f93a; leetcode &#x1f9d7; Leetcode Prime &#x1f3c7; Golang20天教程 &#x1f6b4;‍♂️ Java问题收集园地 &#x1f334; 成长感悟 欢迎大家观看&#xff0c;不执着于追求顶峰&#xff0c;只享受探索过程 Golang 教程07 - Functions …

磁盘挂载、配额、逻辑盘配置

文章目录 一、磁盘挂载1、磁盘分区2、文件系统3、挂载 二、磁盘配额三、逻辑盘配置拓展逻辑卷缩小逻辑卷2、权限3、查找4、软件包、压缩5、常见符号6、克隆虚拟机 I know, i know 地球另一端有你陪我 一、磁盘挂载 分区-格式化&文件系统-磁盘挂载 1、磁盘分区 最多…

HTML+CSS+JS复习回顾

环境搭建 下载VScode&#xff0c;依次下载插件&#xff1a;HTML CSS support、Live Server、Auto Rename Tag 一、HTML篇 HTML通过一系列的标签&#xff08;元素&#xff09;来定义文本、图像、链接等。HTML标签是由尖括号包围的关键字。标签通常成对出现&#xff0c;包括开…

基于SSM的校园二手物品交易平台论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本校园二手物品交易平台就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据…

spring-cloud微服务负载均衡器ribbon

注意&#xff1a;2020年前SpringCloud是采用Ribbon作为负载均衡实现&#xff0c;但是在2020后采用了LoadBalancer替代&#xff0c;所以要查看springboot&#xff0c;springcloud&#xff0c;sprincloudalibaba的版本链接对应&#xff0c;Ribbon负载均衡都是在springboot版本2.4…

【LeetCode热题100】【二叉树】二叉树的层序遍历

题目链接&#xff1a;102. 二叉树的层序遍历 - 力扣&#xff08;LeetCode&#xff09; 借助队列实现广度优先搜索&#xff0c;遍历节点把子树入队 class Solution { public:vector<vector<int>> levelOrder(TreeNode *root) {if (root nullptr)return {};vector…

外观模式(面子模式)

外观模式 文章目录 外观模式什么是外观模式示例 什么是外观模式 外观模式(Facade),为子系统中的一组接口提供一个一致的界面&#xff0c;此模式定义了一个高层接口&#xff0c;这个接口使得这一子系统更加容易使用 Facade 外观类 知道哪些子系统类负责处理请求&#xff0c;将客…

接收RTSP流-断掉重连如何继续显示

问题&#xff1a;写了一个Py脚本接收RTSP视频流并显示&#xff0c;但是RTSP视频流断掉重新恢复时&#xff0c;Py脚本却卡住了&#xff0c;无法继续显示视频。 解决&#xff1a;当RTSP断掉时&#xff0c;释放cap&#xff0c; 如果cap.read()这一步读取时间超过5秒&#xff0c;也…

面试题:volatile

一旦一个共享变量&#xff08;类的成员变量、类的静态成员变量&#xff09;被volatile修饰之后&#xff0c;那么就具备了两层语义&#xff1a; 1. 保证线程间的可见性 保证了不同线程对这个变量进行操作时的可见性&#xff0c;即一个线程修改了某个变量的值&#xff0c;这新值…

探索数据保护的新边界:去标识化加密技术

随着数字化时代的到来&#xff0c;个人信息的保护成为了一个不可忽视的话题。企业和组织在处理大量用户数据的同时&#xff0c;也面临着如何保护用户隐私的挑战。在这样的背景下&#xff0c;去标识化加密技术应运而生&#xff0c;成为了数据安全领域的一大利器。 ### 什么是去…

【攻防世界】wife_wife

原型链污染 源码 app.post(/register, (req, res) > {let user JSON.parse(req.body)if (!user.username || !user.password) {return res.json({ msg: empty username or password, err: true })}if (users.filter(u > u.username user.username).length) {return …

vue3.0 列表页面做缓存

一.设置动态keepalive <router-view v-slot"{ Component, route }"><keep-alive :include"cacheViewsState"><component :is"Component" /></keep-alive></router-view> 可以将要缓存的页面作为vuex全局变量储存…

推进数智化财务管理体系,助力企业降本提效

在数字经济快速发展的今天&#xff0c;数字化能力早已成为企业发展的核心竞争力。在开放、融合的数字经济大背景下&#xff0c;企业该如何将科技深度赋能业务&#xff0c;打造出高质量发展的新引擎&#xff1f;当财务管理缺乏精准化、精确化、及时性的问题逐渐显露&#xff0c;…

【Python】不会优雅的记日志,你又又Out了!!!

1. 引言 在日常开发中&#xff0c;大家经常使用 print 函数来调试我们写的的代码。然而&#xff0c;随着打印语句数量的增加&#xff0c;由于缺乏行号或函数名称&#xff0c;很难确定输出来自何处。而且随着print语句的增多&#xff0c;调试完代码删除这些信息的时候也比较麻烦…

实现首选目标|国内博士后赴新加坡继续从事博士后研究

申请时&#xff0c;V博士尚为国内在站的博士后&#xff0c;其希望在我们的帮助下&#xff0c;加入国外导师先进的课题组&#xff0c;在拓展学术视野的同时&#xff0c;延续自己的科研项目并结题&#xff0c;目标国家首选新加坡。最终我们用新加坡科技研究局&#xff08;A*STA&a…

爬虫之数据神器10---Peewee实现ORM的核心原理

前言: 继续上一篇:爬虫之数据神器9---Peewee集成Django/Flask框架详解-CSDN博客 本章主要讲一些原理方面的东西,帮助大家在项目中 可以更好的理解! 正文: 一、模型定义 在Peewee中&#xff0c;模型的定义是通过模型元类&#xff08;ModelMetaclass&#xff09;实现的。Peew…

TiDB 实战分享丨第三方支付企业的核心数据库升级之路

本文介绍了一家第三方支付企业在面对市场竞争和监管压力的态势下&#xff0c;通过升级核心数据库来提升业务能力的实践。该企业选择 TiDB 分布式数据库&#xff0c;成功将其应用于核心业务、计费、清结算和交易查询等关键系统。TiDB 的水平扩展能力、高可用性和简化数据栈等优势…

electron打包Vue前端

Electron-Forge 打包Vue项目 效果&#xff1a;electronforge可将前端静态页面打包成.exe、.deb和.rpm等&#xff0c;能适配各种平台 示例&#xff1a;Windows环境下将前端 Vue 项目打包成exe文件 打包后的 exe 文件 运行 exe 文件 一、项目准备 开源项目 RouYi 下载 本…