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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于springboot实现房屋租赁管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现房屋租赁系统演示 摘要 房屋是人类生活栖息的重要场所&#xff0c;随着城市中的流动人口的增多&#xff0c;人们对房屋租赁需求越来越高&#xff0c;为满足用户查询房屋、预约看房、房屋租赁的需求&#xff0c;特开发了本基于Spring Boot的房屋租赁系统。 …

python怎么存储数据

在Python开发中&#xff0c;数据存储、读取是必不可少的环节&#xff0c;而且可以采用的存储方式也很多&#xff0c;常用的方法有json文件、csv文件、MySQL数据库、Redis数据库以及Mongdb数据库等。 1. json文件存储数据 json是一种轻量级的数据交换格式&#xff0c;采用完全…

FreeRTOS第四天

1.总结二进制信号量和计数型信号量的区别&#xff0c;以及他们的使用场景。 进制信号量&#xff1a;信号量的数值只有0和1。(用于共享资源的访问&#xff09; 计数型信号量: 计数型信号量的值一般是大于或者等于2 (生产者和消费者模型) 2.使用技术型信号量完成生产者和消费者模…

掌握 Go 语言的 defer 关键字

关注公众号【爱发白日梦的后端】分享技术干货、读书笔记、开源项目、实战经验、高效开发工具等&#xff0c;您的关注将是我的更新动力&#xff01; 在 Go 语言编程中&#xff0c;文件的输入/输出是一个常见的操作。我们知道&#xff0c;每次打开文件后&#xff0c;都需要在操作…

在Linux系统上搭建Android、Linux和Chrome性能监控和Trace分析的系统

perfetto是知名的Android系统性能分析平台。我们还可以用它去分析Linux系统和Chrome&#xff08;需要装扩展&#xff09;。本文我们只介绍如何安装的验证。 部署 我们使用Docker部署perfetto ui系统。 FROM ubuntu:20.04 WORKDIR /perfetto-ui RUN apt-get update -y RUN ap…

蓝桥集训之阶乘分解

蓝桥集训之阶乘分解 核心思想&#xff1a;线性筛质数 筛出每一个质数后 只要将所有质数的1 2 3 … 次方个数都加上即可 #include <iostream>#include <cstring>#include <algorithm>#include <vector>using namespace std;const int N 1e610;int …

MySQL 数据学习笔记速查表(视图、存储过程、事务)

文章目录 十三、视图1、视图是什么&#xff1f;2、视图的特性&#xff1f;3、视图的作用&#xff1f;4、视图的用途&#xff1f;5、视图的使用&#xff1f;1、基本语法2、创建视图3、调用视图4、视图练习(1) 利用试图简化复杂的联结(2) 利用视图重新格式化检索出的数据(3) 利用…

本地Windows打包启动前端后台

本地Windows打包启动前端后台 1、安装jdk Windows JDK安装 2、Nginx 2.1、将 nginx-1.16.1文件夹复制到D:\home\jisapp目录下 2.2、域名证书配置&#xff1a; 将域名证书放到D:\home\jisapp\ssl\2023目录下->配置nginx.conf文件&#xff08;D:\home\jisapp\nginx-1.22.0…

vconsole助力实现在线代码编辑调试

概述 前面有文章monaco-editor做自己的代码测试工具 &#xff0c;本文书接前文&#xff0c;在代码中加入vconsole工具&#xff0c;可以进行代码调试、查看网络、查看元素等。 效果 vconsole简介 vconsole是一个轻量、可拓展、针对手机网页的前端开发者调试面板。跟框架无关的…

轮播图实现

基于html、js实现网页中常见的轮播图 html: <div id"box"><img src"./images/1.jpg" alt""><img src"./images/2.jpg" alt""><img src"./images/3.jpg" alt""><img src&q…

Minikube本地搭建单节点Kubernetes集群

1、什么是 Minikube Minikube 是一个开源工具&#xff0c;旨在为开发者提供一种便捷的方式在本地环境中搭建单节点的 Kubernetes 集群。它主要用于开发、测试和学习 Kubernetes 应用程序&#xff0c;无需依赖大型的硬件资源或复杂的多节点集群配置。minikube 使用轻量级虚拟化技…

CountDownTimer 倒计时不准确问题解决

前言 最近笔者在实现一共和倒计时有关的功能&#xff0c;使用CountDownTimer实现。然而&#xff0c;在测试的时候发现&#xff0c;倒计时经常发现跳秒、不出现1的情况&#xff0c;因此对这方面进行了一些了解。本文准备介绍一下CountDownTimer倒计时不准确的原因&#xff0c;以…

【进阶六】Python实现SDVRPTW常见求解算法——遗传算法(GA)

基于python语言&#xff0c;采用经典遗传算法&#xff08;GA&#xff09;对 带硬时间窗的需求拆分车辆路径规划问题&#xff08;SDVRP&#xff09; 进行求解。 目录 往期优质资源1. 适用场景2. 代码调整2.1 需求拆分2.2 需求拆分后的服务时长取值问题 3. 求解结果4. 代码片段参…

【哈希表专题】(1. 两数之和 面试题 01.02. 判定是否互为字符重排 217. 存在重复元素 219. 存在重复元素 II 49. 字母异位词分组)

文章目录 哈希表1. 两数之和面试题 01.02. 判定是否互为字符重排217. 存在重复元素219. 存在重复元素 II49. 字母异位词分组 哈希表 哈希表是什么&#xff1a;存储数据的容器 作用&#xff1a;快速查找某个元素。O(1) 当我们需要频繁的查找某一个数的时候&#xff0c;可以使…

YOLO火灾烟雾检测数据集:20000多张,yolo标注完整

YOLO火灾烟雾检测数据集&#xff1a;一共20859张图像&#xff0c;yolo标注完整&#xff0c;部分图像应用增强 适用于CV项目&#xff0c;毕设&#xff0c;科研&#xff0c;实验等 需要此数据集或其他任何数据集请私信

蓝桥杯备考2

P8839 [传智杯 #4 初赛] 组原成绩 题目描述 花栗鼠科技大学&#xff08;Hualishu University of Science and Technology, HUST&#xff09;的计算机组成原理快要出分了。你现在需要计算你的组原成绩如何构成。 具体来说&#xff0c;组原成绩分为三部分&#xff0c;分别是平…

算法学习系列(四十六):迭代加深、双向DFS

目录 引言概念一、加成序列二、送礼物 引言 本文主要讲了&#xff0c; D F S DFS DFS 的另外两种优化&#xff0c;分别是迭代加深和双向 D F S DFS DFS &#xff0c;思路还是非常清晰明了的&#xff0c;只要会写 D F S DFS DFS 那么这些剪枝和优化其实还是非常的容易的&…

多线程学习-线程安全

目录 1.多线程可能会造成的安全问题 2. static共享变量 3.同步代码块 4.同步方法 5.使用Lock手动加锁和解锁 6.死锁 1.多线程可能会造成的安全问题 场景&#xff1a;三个窗口同时售卖100张电影票&#xff0c;使用线程模拟。 public class MyThread extends Thread{//tic…

AI结合机器人的入门级仿真环境有哪些?

由于使用真实的机器人开发和测试应用程序既昂贵又费时&#xff0c;因此仿真已成为机器人应用程序开发中越来越重要的部分。在部署到机器人之前在仿真中验证应用程序可以通过尽早发现潜在问题来缩短迭代时间。通过模拟&#xff0c;还可以更轻松地测试在现实世界中可能过于危险的…

Linux文件搜索工具(gnome-search-tool)

opensuse下安装: sudo zypper install gnome-search-tool 操作界面: