C++智能指针shared_ptr用法

news2024/9/22 13:31:12

目录

  • shared_ptr功能介绍
  • shared_ptr提供的接口
  • shared_ptr初始化
  • shared_ptr管理指针的构造和析构
  • shared_ptr获取原始指针
  • shared_ptr的线程安全
  • shared_ptr应用之enable_shared_from_this

写在前面的总结:
一个shared_ptr对象管理一个指针(new T,在堆空间),多个shared_ptr对象可以管理同一个指针,只有某个shared_ptr对象第一次初始化指针时才执行指针的构造函数,管理同一个指针的shared_ptr对象个数称为引用计数,这个引用计数保存在每个管理该指针的shared_ptr对象中,当引用计数为0时,这个指针执行析构函数释放;shared_ptr对象也可以管理空指针,此时引用计数为0。

shared_ptr功能介绍

智能指针和普通指针用法相似,智能指针的本质是一个模板类,对普通指针进行了封装,通过在构造函数中初始化分配内存,在析构函数中释放内存,达到自己管理内存,不需要手动管理内存的效果,避免了忘记释放内存而导致的内存泄露。

shared_ptr 是C++11提供的一种智能指针类,可以在任何地方都不使用时自动删除相关指针,从而帮助彻底消除内存泄漏和悬空指针的问题。
它遵循共享所有权的概念,即不同的 shared_ptr 对象可以与相同的指针相关联,并在内部使用引用计数机制来实现这一点。
每个 shared_ptr 对象在内部指向两个内存位置:
1、指向对象的指针。
2、用于控制引用计数数据的指针。
共享所有权如何在参考计数的帮助下工作:
1、当新的 shared_ptr 对象与指针关联时,则在其构造函数中,将与此指针关联的引用计数增加1。
2、当任何 shared_ptr 对象超出作用域时,则在其析构函数中,它将关联指针的引用计数减1。如果引用计数变为0,则表示没有其他 shared_ptr 对象与此内存关联,在这种情况下,它使用delete函数删除该内存。

shared_ptr是以类模板的方式实现的,shared_ptr(其中 T 表示指针指向的具体数据类型)的定义位于头文件。

shared_ptr提供的接口

shared_ptr是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。
shared_ptr的存储指针和引用计数指针是一一对应的,即shared_ptr里存的是存储指针,对应的引用计数指针就是对stored pointer的加一,因此shared_ptr在其内部,给每个资源都维护着一份计数,用来记录该份资源被几个对象共享。
在这里插入图片描述

shared_ptr初始化

构造函数初始化

///1.构造函数初始化
//传入空指针或什么都不传,构造出空智能指针,其初始引用计数方式为0
std::shared_ptr<int>  p0(nullptr);
printf("p0.use_count=%ld\n",p0.use_count());//p0.use_count=0

//构造函数初始化,指向一个存有5这个int类型数据的堆内存空间
std::shared_ptr<int> p1(new int(5));
printf("p1=%p\n",&p1);//p1=0x7ffc24046f10
printf("p1=%d\n",*p1);//p1=5
printf("p1.use_count=%ld\n",p1.use_count());//1

std::shared_ptr<int> p2(p1);//p1和p2都指向那个存有int型5的堆内存空间,堆内存的引用次数会加1
printf("p1.use_count=%ld\n",p1.use_count());//2
printf("p2.use_count=%ld\n",p2.use_count());//2

std::shared_ptr<int> p3=p0;//P0为空,则P3也为空,其引用计数依然为0
printf("p3.use_count=%ld\n",p3.use_count());//0

//补充
//可以把原始指针传参构造shared_ptr对象,此时原始指针没有new或没有初始化赋值,use_count都是1
int *p11 = new int;//如果没有=new int,下面p12.use_count还是1,但执行打印时会crash
//p11 = nullptr;//如果p11赋值为nullptr,下面p12.use_count还是1,但执行打印时会crash
std::shared_ptr<int> p12(p11);
printf("p12.use_count=%ld\n",p12.use_count());//1
printf("*p12=%d\n",*p12);//*p12=1345903632(原始指针new了,但没有初始化,则打印未知值)

std::make_shared 初始化

///2.std::make_shared 初始化
std::shared_ptr<int> p4 = std::make_shared<int>(); //1.定义一个空的智能指针
std::shared_ptr<int> p5= std::make_shared<int>(10);//2.创建指针,并明确指向
auto p6 = std::make_shared<std::vector<int>>();//3.auto关键字代替std::shared_ptr,p8指向一个动态分配的空vector<int>

reset初始化

///3.reset初始化
//调用reset(new xxx())重新赋值时,智能指针首先是生成新对象,然后将旧对象的引用计数减1(当然,如果发现引用计数为0时,则析构旧对象),然后将新对象的指针交给智能指针保管。
std::shared_ptr<int> p7(new int(20));
std::shared_ptr<int> p8(p7);
std::shared_ptr<int> p9(p7);
printf("p9.use_count=%ld\n",p8.use_count());//3
p7.reset(new int(21));//当为reset传递一个新申请的堆内存时,则调用该函数的 shared_ptr 对象会获得该存储空间的所有权,并且引用计数的初始值为1;其所指向的原堆内存引用计数减1
printf("p9.use_count=%ld\n",p8.use_count());//2
p9.reset();//当reset没有实参时,该函数会使当前 shared_ptr 所指堆内存的引用计数减 1,同时将当前对象重置为一个空指针
if(p9 == nullptr)
    printf("p10 == nullptr\n");//p10 == nullptr
printf("p9.use_count=%ld\n",p8.use_count());//1

shared_ptr管理指针的构造和析构

下面例子介绍shared_ptr是如何管理所指向指针(堆空间)的构造和析构的。

class book
{
public:
    book(int v) {//构造函数
        value = v;
        std::cout << "cons book value=" <<value<< std::endl;
    }
    ~book() {//析构函数
        std::cout << "desc book value=" <<value<< std::endl;
    }
    int value;
};

{
    std::shared_ptr<book> b1(new book(100));//cons book value=100
    std::shared_ptr<book> b2(b1);//只增加了引用计数,没有新增构造book,use_count=2
    b2.reset(new book(200));//cons book value=200(新构造book200,原book100计数变为1)
    b1.reset(new book(300));//cons book value=300   desc book value=100(新构造book300,原book100引用计数变为0,执行析构)

    printf("end\n");//准备离开作用域
}

打印,shared_ptr初始化时如果生成了新的指针,则执行指针的构造函数,如果指向该指针的shared_ptr对象引用计数为0时,执行该指针的析构函数;
如果shared_ptr对象超出了作用域,则引用计数减1,引用计数为0时执行析构。

cons book value=100
cons book value=200
cons book value=300
desc book value=100
end
desc book value=200
desc book value=300

shared_ptr获取原始指针

智能指针提供了get()成员函数,用来执行显示转换,返回智能指针内部的原始指针。

std::shared_ptr<int> p10(new int(300));
int *pn = p10.get();
printf("pn=%d\n",*pn);//pn=300

shared_ptr的线程安全

1、shared_ptr不是线程安全的;
2、在多线程下,不能保证new出来一个对象一定能被放入shared_ptr中,也不能保证智能指针管理的引用计数的正确性;
3、同一个shared_ptr对象可以被多线程同时读取,不同的shared_ptr对象可以被多线程同时修改,但同一个shared_ptr对象不能被多线程直接修改;
4、在创建一个shared_ptr时,需要使用C++11提供的make_shared模板,make_shared创建shared_ptr只申请一次内存,避免了上述错误,也提高了性能,同时在读写操作时,需要加锁。

shared_ptr应用之enable_shared_from_this

C++11 开始支持 enable_shared_from_this,它是一个模板类,定义在头文件 ,其原型为:

template< class T > class enable_shared_from_this;

enable_shared_from_this 能让一个指针(假设其名为 t ,且已被一个 std::shared_ptr 对象 pt 管理)安全地生成其他额外的 std::shared_ptr 实例(假设名为 pt1, pt2, … ) ,它们与 pt 共享对象 t 的所有权。
若一个类 T 继承 std::enable_shared_from_this ,则会为该类 T 提供成员函数: shared_from_this 。
当 T 类型对象 t 被一个为名为 pt 的 std::shared_ptr 类对象管理时,调用 T::shared_from_this 成员函数,将会返回一个新的 std::shared_ptr 对象,它与 pt 共享 t 的所有权。

使用场景
1、当类A被share_ptr管理,且在类A的成员函数里需要把当前类对象作为参数传给其他函数时,就需要传递一个指向自身的share_ptr。
2、在异步调用中,可能会使用到之前存在的变量,为了保证该变量在异步调用中一直有效,可以传递一个指向自身的share_ptr给异步函数,这样share_ptr所管理的对象就不会析构(引用计数至少>=1,保活)。

#include <iostream>
class Good : public std::enable_shared_from_this<Good> // 注意:继承
{
public:
    Good(){std::cout << "Good::Good() called" << std::endl; }
    std::shared_ptr<Good> getptr() {
        return shared_from_this();
    }
    ~Good() { std::cout << "Good::~Good() called" << std::endl; }
};
MainWindow::MainWindow(QWidget *parent)    : QMainWindow(parent)    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    std::shared_ptr<Good> gp1(new Good());
    std::cout << "gp1.use_count() = " << gp1.use_count() << std::endl;//1
    std::shared_ptr<Good> gp2 = gp1->getptr();
    std::cout << "gp1.use_count() = " << gp1.use_count() << std::endl;//2
}

打印

Good::Good() called
gp1.use_count() = 1
gp1.use_count() = 2
Good::~Good() called

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

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

相关文章

TCP/IP五层协议栈(3)

1.网络层 1.1.IP协议 IP协议格式:报头数据 4位版本 :IP协议的版本号.当前只有两个取值,4和6(0100 0110).( 这里讨论IPv4 )4位首部长度 :IP报头和TCP类似,都是可变的,带有选项.8位TOS :只有4位有效,那四位TOS分别表示( 最小延时,最大吞吐量,最高可靠性,最小成本 )(同一时刻只能…

使用 Qt for Android 获取并利用手机传感器数据(下篇)使用C++实现功能

在上一篇&#xff0c;我们搭建了开发环境。本篇&#xff0c;使用C代码真正实现功能。我们使用UDP协议从手机上指定发送的目的地、端口。效果如下图&#xff0c;完整工程参考https://gitcode.net/coloreaglestdio/qtcpp_demo/-/tree/master/android/sensors2pc&#xff1a; 移动…

全志T3 ARM+Ethercat+Codesys工业控制器设计方案

目前codesys EtherCAT驱动 做运动控制很有优势。现在总线式运动控制基本都是这种配置。 Codesys 号称PLC界的安卓&#xff0c;国内造PLC的 基本都用Codesys内核了。 如&#xff1a;汇川 &#xff0c;合信&#xff0c; 和利时 &#xff0c;英威腾&#xff0c; 台达。 包…

原子范数初探:以到达角估计为例

到达方向&#xff08;Direction-of-arrival, DOA&#xff09;估计是指从形成传感器阵列的多个接收天线的输出中检索若干电磁波/源的方向信息的过程。DOA估计是阵列信号处理中的一个主要问题&#xff0c;在雷达、声纳、无线通信中有着广泛的应用。 基本数学模型 考虑KKK个窄带…

Java项目:ssm流浪猫狗救助管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 流浪猫狗救助管理系统。该项目分为前后台&#xff1b; 前台主要功能包括&#xff1a;会员的注册登陆,流浪猫狗知识&#xff0c;领养中心&#…

[附源码]计算机毕业设计JAVA学生实习管理系统

[附源码]计算机毕业设计JAVA学生实习管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybati…

80W美团架构师整理分享出了Spring5企业级开发实战文档

前言 都说程序员工资高、待遇好&#xff0c; 2022 金九银十到了&#xff0c;你的小目标是 30K、40K&#xff0c;还是 16薪的 20K&#xff1f;作为一名 Java 开发工程师&#xff0c;当能力可以满足公司业务需求时&#xff0c;拿到超预期的 Offer 并不算难。然而&#xff0c;提升…

U盘插入提示格式化才能使用,但里面有数据无法复制出来怎么解决?

U盘作为移动储存硬盘&#xff0c;避免不了出现各种问题&#xff0c;特别是莫名提示格式化&#xff0c;无法打开&#xff0c;要使用的话只能先将其格式化。 只要电脑还能正常识别出U盘&#xff0c;那都是有概率恢复出来数据的。先不要点“格式化”&#xff01; 如果一旦出现点…

4.JVM垃圾收集机制

1. 垃圾收集算法 1.1 分代收集理论 当前虚拟机的垃圾收集都采用分代收集算法&#xff0c;这种算法&#xff0c;没有什么新的思想&#xff0c;只是根据对象存活周期的不同将内存分成几块。一般将JAVA堆分为新生代、老年代&#xff0c;这样我们就可以根据各个年代的特点选择合适…

【注意力机制】Self-attention注意力机制理论知识

注意力机制目录输入输出类别&#xff08;N指向量个数&#xff09;&#xff1a;Self-attention引入self-attention架构self-attention怎么产生bbb例子&#xff1a;产生b1b^{1}b1例子&#xff1a;产生b2b^{2}b2self-attention 总结&#xff1a;Multi-head Self-attentionPosition…

使用 Learner Lab - 使用 API Gateway 与 Lambda 上传图片到 S3

使用 Learner Lab - 使用 API Gateway 与 Lambda 上传图片到 S3 AWS Academy Learner Lab 是提供一个帐号让学生可以自行使用 AWS 的服务&#xff0c;让学生可以在 100 USD的金额下&#xff0c;自行练习所要使用的 AWS 服务&#xff0c;如何进入 Learner Lab 请参考 使用 Lear…

【车载开发系列】UDS诊断---读取数据($0x22)

【车载开发系列】UDS诊断—读取数据&#xff08;$0x22&#xff09; UDS诊断---读取数据&#xff08;$0x22&#xff09;【车载开发系列】UDS诊断---读取数据&#xff08;$0x22&#xff09;一.概念定义二.报文格式1&#xff09;请求2&#xff09;肯定响应3&#xff09;否定响应三…

Vue中$nextTick实现源码解析

这篇文章主要为大家介绍了Vue中$nextTick实现源码解析&#xff0c;有需要的朋友可以借鉴参考下&#xff01; 先看一个简单的问题 {{ text }} 此时打印的结果是什么呢&#xff1f;是 old。如果想让它打印 new&#xff0c;使用 nextTick 稍加改造就可以 this.$nextTick(() >…

【计组笔记】06_指令系统

5.1 指令系统概述及指令格式 1. 指令的基本概念 2. 指令的分类 根据计算机层次结构分类 根据指令中地址码字段的个数分类 根据指令中操作数的物理位置分类 根据指令的功能分类 3. 指令格式 4. 指令的再认识 5. 指令格式举例 5.2 寻址方式及指令寻址 1. 寻址方式的概念 2.…

[论文阅读] 颜色迁移-Correlated Color Space

[论文阅读] 颜色迁移-Correlated Color Space 文章: Color transfer in correlated color space, [paper], [matlab code], [opencv code] 1-算法原理 本文算法比较简单, 其原理是把原始图像本身的空间分布进行归一化, 然后通过旋转平移缩放等变换, 变换到目标图像的空间分布…

WMS手动配货和自动配货的区别

手动配货 不知道配货流程的朋友可以看一下前面的文章链接: 深入浅出WMS之出库流程里面有对出库的解释说明&#xff0c;其中也有对配货的解释。前端页面也可以在前面的那篇文章中看到&#xff0c;这里我们来说一下后端部分。 查 手动配货是选中出库单的某条数据&#xff0c;然…

一文教你从Linux内核角度探秘JDK NIO文件读写本质(上)

1. 前言 在深入讲解Netty那些事儿之从内核角度看IO模型一文中曾对 Socket 文件在内核中的相关数据结构为大家做了详尽的阐述。 Socket内核结构.png 又在此基础之上介绍了针对 socket 文件的相关操作及其对应在内核中的处理流程&#xff1a; 系统IO调用结构.png 并与 epoll 的…

由阿里三位专家撰写:数据库高效优化:架构、规范SQL技巧文档

引言 学习是一种基础性的能力。然而&#xff0c;“吾生也有涯&#xff0c;而知也无涯。”&#xff0c;如果学习不注意方法&#xff0c;则会“以有涯随无涯&#xff0c;殆矣”。 学习就像吃饭睡觉一样&#xff0c;是人的一种本能&#xff0c;人人都有学习的能力。我们在刚出生…

[附源码]计算机毕业设计springboot作业管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

词袋模型(Bag-of-words model)

词袋模型词袋模型简介示例计算机视觉中的词袋模型词袋模型 简介 词袋模型&#xff08;Bag-of-words model&#xff09;是用于自然语言处理和信息检索中的一种简单的文档表示方法。通过这一模型&#xff0c;一篇文档可以通过统计所有单词的数目来表示&#xff0c;这种方法不考…