C++标准库的智能指针:shared_ptr、weak_ptr和unique_ptr

news2024/10/6 6:50:43

文章目录

  • 智能指针shared_ptr模版类
  • week_ptr模版类
  • unique_ptr

C++中是没有内存回收机制的,我在之前的一篇文章中提到使用指针的一些基本方法。C++在std标准库中也提供了三种封装过的指针模版类,称作为智能指针:

  • shared_ptr
  • unique_ptr
  • week_ptr

我这里没打算详细介绍这三个指针的使用方式,主要是想从上一篇文章中提到的,在使用指针的过程中,容易出现的几个问题,如果是用智能指针的话,可以如何去解决。当然,也首先要介绍一下这三个指针的基本逻辑。

智能指针的核心实现技术是引用计数,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存。

先从shared_ptr说起。

智能指针shared_ptr模版类

直接上一段demo代码:

 #include <stdio.h>
 #include <memory>

using namespace std;

//自定义释放规则
void deleteInt(int* p) {
    printf("abdcd\n");
    delete []p;
}

int main()
{
    auto aPtr = new float[10];
    *aPtr = 1.0f;

    printf("%f\n", *aPtr);

    std::shared_ptr<int> p1(new int(10), deleteInt);
    std::shared_ptr<int> p2(p1);
    std::shared_ptr<int> p3(p1);

    *p1 = 1;

    printf("%d\n", *(p1.get()));
    printf("%d\n", *(p2.get()));

    printf("use_count, %d\n", p1.use_count());
    printf("use_count, %d\n", p2.use_count());
    printf("use_count, %d\n", p3.use_count());

    p3.reset();

    printf("use_count, %d\n", p1.use_count());
    printf("use_count, %d\n", p2.use_count());
    printf("use_count, %d\n", p3.use_count());
    
    p2.reset();

    printf("use_count, %d\n", p1.use_count());
    printf("use_count, %d\n", p2.use_count());
    printf("use_count, %d\n", p3.use_count());

    p1.reset();

    printf("use_count, %d\n", p1.use_count());
    printf("use_count, %d\n", p2.use_count());
    printf("use_count, %d\n", p3.use_count());
}
  • std::shared_ptr p1(new int(10), deleteInt);这里是指定了一个删除器,也就是说当引用计数降到0时,调用的释放内存的函数。
  • 只有当引用计数降为0的时候才会调用删除器

输出结果是:

1.000000
1
1
use_count, 3
use_count, 3
use_count, 3
use_count, 2
use_count, 2
use_count, 0
use_count, 1
use_count, 0
use_count, 0
abdcd
use_count, 0
use_count, 0
use_count, 0

来分析一下这个结果:
上面的代码实际上就是形成这样一个内存布局,三个shared_ptr指向了同一块内存。
在这里插入图片描述

  • 这一块内存当前有3个指针引用,也就是引用计数为3。此时不管是p1,p2,p3,调用use_count的时候,都是输出的3。
  • 第一个是p3.reset,这个函数执行后,就是p3到memory的这条线没有了。memory的引用计数就变成2了,所以p1和p2的use_count的输出就变成了2,而p3因为已经和memory这个内存没有关系了,所以use_count就变成了0.
  • 第二个p2.reset以后,逻辑和上一条一样,p2,p3都变成了0,memory的引用计数变成了1.
  • 当p1.reset执行以后,这块内存引用计数就变成0了,就会调用删除器了。

实际上,上图中的memory部分可以拆成两个部分:

  • data field,就是数据部分,比如上面代码中的new int(10),一块十个正形大小的内存区域。
  • control block。控制块,这个控制块就是shared_ptr的关键数据结构了,里面包含了use_count(强引用计数),weak_count(弱引用计数),删除器,构造器之类的一些内容。
  • 控制块是一块单独的在堆上分配的内存空间。
  • 回到上面的例子中,p3.reset相当于是p3这个变量的指向control block赋值为空(就是上面图中提到的,那条指向的线断了),于是通过p3.use_count就会返回0,但是在这一个control block中,p3执行reset之后,只是把引用计数减一,不会影响到p1和p2的返回。
  • 引用计数的增加和减少是原子操作,可以在多线程中使用,是线程安全的(只是说这个操作是原子的,不是说所有的通过shared_ptr的操作都是线程安全的,是两个完全不同的概念)。
  • 强烈推荐使用make_shared函数来初始化一个shared_ptr。
  • 赋值运算符在shared_ptr中是一个移动拷贝函数,不会创建一个新的对象,只是让两个不同的指针指向同一个位置。
    下面这段代码中,只会初始化两次构造函数,在p2 = p1这个地方是不会创建新的对象的。
    class Base
      {
      public:
          Base(){printf("base construct\n");}
          ~Base(){printf("base destruct\n");}
      };
    
      class Derived: public Base
      {
      public:
          Derived(){printf("Derived construct\n");}
          ~Derived(){printf("Derived destruct\n");}
      };
    
      int main()
      {
          std::shared_ptr<Derived> p1 = std::make_shared<Derived>();
    
          std::shared_ptr<Derived> p2 = p1;
    
          std::shared_ptr<Derived> p3(new Derived());
    
          printf("p1 use count: %d\n", p1.use_count());
          printf("p2 use count: %d\n", p2.use_count());
          printf("p3 use count: %d\n", p3.use_count());
      }
    

week_ptr模版类

再来看一下weak_ptr,在上面的shared_ptr中,实际上无法准确的引用一个智能指针变量去判断某一片数据内存是否被使用,或者说准确的引用数是多少,因为在程序运行过程中,每一个shared_ptr的智能指针都可能会执行reset(),执行之后,这块内存的引用数就不能通过这个变量去调用use_count来获取了,这样在程序中非常的不方便。
那么,c++17中引入了week_ptr这个指针来解决这个问题。
week_ptr被称作为一个“观察者”,这个观察者可以做用于某一块数据指针,这个观察者可以“观察到”某一块内存空间上的引用者,或者说某一些shared_ptr对应的某一个control block。
在这里插入图片描述

week_ptr有下面几个特征,来说明它的“观察者”身份:

  • shared_ptr重载了*和->运算符,还有get函数来获得内存区域的控制权和试用权,而week_ptr没有这些东西,无法直接使用内存空间。
  • 不管shared_ptr如何的reset和构建,少一个指向内存区域的线,通过week_ptr调用use_count()就会减一,反之则加一。
  • week_ptr自己释放和构建,是不会影响引用计数的。
  • week_ptr可以通过lock函数,去获取一个可以访问这篇内存空间的shared_ptr智能指针。当然,如果所有指向这篇内存的shared_ptr都已经被释放了,那么lock函数返回空。
  • lock获得的shared_ptr也会增加一个引用计数。
  • 只能通过shared_ptr或者一个week_ptr来创建week智能指针,不能直接初始化。

还是上代码来看:

int main()
{
    std::shared_ptr<int> p1 = std::make_shared<int>();

    std::shared_ptr<int> p2 = p1;

    std::weak_ptr<int> wp1(p1);
    *p1 = 1;

    printf("content is: %d\n", *p1);

    printf("week ptr cnt is: %d\n", wp1.use_count());
    std::shared_ptr<int> t = wp1.lock();
    printf("content though week ptr is: %d\n", *t);

    printf("p1 address is %x\n", p1.get());
    printf("p2 address is %x\n", p2.get());
    printf("wp1 address is %x\n", wp1.lock().get());

    t.reset();
    p2.reset();
    
    t = wp1.lock();
    printf("content though week ptr is: %d\n", *t);
    t.reset();
    printf("week ptr cnt is: %d\n", wp1.use_count());
    p1.reset();
    
    t = wp1.lock();
    if(t)
    {
        printf("content though week ptr is: %d\n", *t);
    }
    else
    {
        printf("all ptr is released\n");
    }
    t.reset();
    printf("week ptr cnt is: %d\n", wp1.use_count());
}

输出结果为:

content is: 1
week ptr cnt is: 2
content though week ptr is: 1
p1 address is 65c059a8
p2 address is 65c059a8
wp1 address is 65c059a8
content though week ptr is: 1
week ptr cnt is: 1
all ptr is released
week ptr cnt is: 0

所以说,week_ptr的作用之一就是担任某一块内存空间使用的“观察者”,程序员可以通过这个“观察者”来判断空间的使用情况。

unique_ptr

除了上面两个智能指针模板,标准库还提供了一个unique_ptr的模板类。这个和shared_ptr很类似。
但是unique_ptr的基本逻辑保证了同一块内存只有一个unique_ptr指针指向。
unique_ptr不能通过拷贝构造函数来生成,必须通过移动构造函数来生成:
看下下面的代码:

int main()
{
    std::unique_ptr<int> u_ptr(new int(5));

    *u_ptr = 10;

    printf("u_ptr: %d\n", *u_ptr);

    // std::unique_ptr<int> u1_ptr(u_ptr); //无法编译通过,必须使用移动构造函数
    
    std::unique_ptr<int> u1_ptr = std::move(u_ptr);

    // printf("u_ptr: %d\n", *u_ptr); // 编译可以通过,但是因为u_ptr已经不再指向这个地址了,会出现段错误
    printf("u1_ptr: %d\n", *u1_ptr);
}

输出结果为:

u_ptr: 10
u1_ptr: 10

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

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

相关文章

JVM性能调优详解

前面我们学习了整个JVM系列&#xff0c;最终目标的不仅仅是了解JVM的基础知识&#xff0c;也是为了进行JVM性能调优做准备。这篇文章带领大家学习JVM性能调优的知识。 性能调优 性能调优包含多个层次&#xff0c;比如&#xff1a;架构调优、代码调优、JVM调优、数据库调优、操…

最优二叉树(哈夫曼树)

一、最优二叉树 1、定义 官方定义&#xff1a;在权值为w1&#xff0c;w2&#xff0c;…&#xff0c;wn的 n个叶子所构成的所有二叉树中&#xff0c;带权路径长度最小(即代价最小)的二叉树称为最优二叉树或哈夫曼树。 通俗来讲&#xff0c;就是给定N个权值作为N个叶子结点&…

仿牛客论坛项目总结

一.数据库中每一张表有哪些字段 user表 用户表 &#xff08;1&#xff09;id 用户的id &#xff08;2&#xff09; username 用户名 &#xff08;3&#xff09;password 密码 &#xff08;4&#xff09;salt 盐 &#xff08;5&#xff09;emai邮箱 &#xff08;6&…

PAT甲级1008 Elevator C++/C语言

1008 Elevator 分数 20 作者 CHEN, Yue 单位 浙江大学 The highest building in our city has only one elevator. A request list is made up with N positive numbers. The numbers denote at which floors the elevator will stop, in specified order. It costs 6 seconds …

联邦学习将如何影响您的日常生活?

人工智能 (AI) 被认为是下一次工业革命的最大创新之一&#xff0c;其中包括机器学习。另一方面&#xff0c;随着原油和电力成为现代工业的基础资源&#xff0c;数据成为人工智能和机器学习的关键要素。 数据隐私与需求之间的冲突 训练的数据样本的大小决定了可用于增强 AI 性能…

CPT203-Software Engineering(2)

文章目录5. Scrum Framework5.1 Scrum Roles5.2 Scrum Activities and Artifacts6. Requirements Engineering6.1 User requirements and system requirements6.2 Functional & Non-functional requirements6.2.1 Functional requirements6.2.2 Non-functional requirement…

第一章:C++算法基础之基础算法

系列文章目录 文章目录系列文章目录前言一、排序&#xff08;1&#xff09;快速排序核心思想思路分析模板&#xff08;2&#xff09;归并排序核心思想思路分析模板稳定性时间复杂度二分查找&#xff08;1&#xff09;整数二分核心思想思路分析模板&#xff08;2&#xff09;浮点…

jetson nano系统引导安装(无外设安装方式)

文章目录一.硬件设置二.系统设置一.硬件设置 插入烧写好系统的SD卡将micro USB线接到jetson nano上&#xff0c;另一端USB A接到电脑上为jetson nano插入电源&#xff0c;开机等待电脑检测到如下盘符说明jetson nano连接成功 二.系统设置 进入电脑的设备管理器&#xff0c;查…

【linux】三种权限的使用和更改、粘滞位和yum的使用

目录 1.权限问题 ①什么是权限&#xff1f; ②小问题 ③默认权限 ④如何更改“人”的权限呢&#xff1f; ⑤更改权限的八进制方案 ⑥强制改权限里的“人”&#xff08;权限人文件属性&#xff09; 2.粘滞位 2.yum的使用 1.权限问题 ①什么是权限&#xff1f; 权限人&a…

HTTP协议解析

HTTP概述 HTTP (全称为 "超文本传输协议") 是一种应用非常广泛的应用层协议~~我们平时打开一个网站, 就是通过 HTTP 协议来传输数据的。 HTTP工作过程&#xff1a; 当我们在浏览器中输入一个 "网址"&#xff0c;此时浏览器就会给对应的服务器发送一个 H…

CTF中的PHP特性函数(上)

前言 对于PHP大家一定不陌生&#xff0c;但你知道PHP在CTF中是如何考察的吗&#xff0c;本文给大家带来的是通过PHP特性来进行CTF比赛中解题出题的知识&#xff0c;会介绍一下CTF中常见的php特性以及围绕该知识点的相关案例&#xff0c;因为内容过多这里分成上中下三篇来讲&am…

操作系统的特征

文章目录&#x1f380;前言&#xff1a;本篇博客知识总览&#x1f354;并发&#x1f387;概念&#xff1a;&#x1f354;共享&#x1f387;概念&#xff1a;&#x1f354;虚拟&#x1f387;概念&#xff1a;&#x1f354;异步&#x1f387;概念&#xff1a;&#x1f3f3;️‍&a…

ThinkPHP 多应用模式之Api路由分组+中间件

ThinkPHP 6.1 在多应用模式下实现API路由分组中间件验证业务 目录 1.创建中间件文件 2.迁移中间件到子应用目录中 3.编辑中间件验证业务 修改命名空间 编写handle处理代码 4.注册中间件 编辑中间件文件 TP内置中间件 5.设置路由分组 优化相同控制器前缀 最终效果&am…

【信息论与编码 沈连丰】第六章:连续信息和连续信道

【信息论与编码 沈连丰】第六章&#xff1a;连续信息和连续信道第六章 连续信息和连续信道6.1 连续消息的信息6.2 连续消息在信道上的传输问题6.3 香农信道容量公式6.4 连续消息的识别和理想接收机6.5 连续信源的数字处理及其编码第六章 连续信息和连续信道 6.1 连续消息的信息…

在 KubeSphere 上部署 OpenLDAP 并进行对接使用

在 KubeSphere 上部署 OpenLDAP 并进行对接-进阶背景前置条件KubeSphere 中部署 LDAP部署 LDAP 应用ApacheDirectoryStudio 验证 LDAP下载部署 ApacheDirectoryStudioApacheDirectoryStudio 测试 LDAP创建 Ldap Search &#xff0c;KS 对接时可选择使用KubeSphere 对接 LDAPHar…

【区块链 | EVM】深入理解学习EVM - 深入Solidity数据存储位置:内存

图片来源: Mech Mind on Unsplash 这是深入Solidity数据存储位置系列的另一篇。在今天的文章中,我们将学习EVM内存的布局,它的保留空间,空闲内存指针,如何使用memory引用来读写内存,以及使用内存时的常规最佳做法。 我们将使用 Ethereum Name Service (ENS)中的合约代码…

实模式和保护模式的区别

实模式和保护模式的区别 实模式和保护模式的来历 最早期的8086 CPU只有一种工作方式 ---- 实模式。数据总线为16位&#xff0c;地址总线为20位。实模式下所有寄存器都是16位。 从80286开始就有了保护模式&#xff0c;从80386开始CPU数据总线和地址总线均为32位&#xff0c;而且…

开发板测试手册——SPI FLASH 读写、USB WIFI 模块(2)

目录 1.8 SPI FLASH 读写测试 20 1.9 USB 接口读写测试 21 1.10 网络接口测试 23 1.10.1 网络连通测试 23 1.10.2 网络速度测试 25 2 网络静态 IP 设置 27 3 USB WIFI 模块测试 31 3.1 WIFI STA 功能测试 32 3.2 WIFI AP 功能测试 35 3.3 USB WIFI 驱动编译 39 前 言…

scikit-learn线性模型之线性回归

scikit-learn线性模型之线性回归线性回归参考文献线性回归 有监督学习中主要解决两个问题&#xff0c;一个是分类&#xff0c;另一个是回归。 在回归问题中&#xff0c;我们需要利用我们已知的特征 x1,x2,...,xpx_1,x_2,...,x_px1​,x2​,...,xp​ 去预测我们的目标变量 yyy 。…

隐私计算一体机,金融大数据规模商用的催化剂

一股隐私计算的浪潮正席卷金融行业。 银行通过隐私计算引入外部不动产数据&#xff0c;与行内贷款企业的时点贷款余额、注册资本等数据联合建立企业贷中预警监测模型&#xff0c;提升银行风险监测业务能力&#xff1b;银行利用联邦学习与互联网公司的客户特征数据完成联合建模…