【C++入门到精通】智能指针 shared_ptr 简介及C++模拟实现 [ C++入门 ]

在这里插入图片描述

阅读导航

  • 引言
  • 一、简介
  • 二、成员函数
  • 三、使用示例
  • 四、C++模拟实现
  • 五、std::shared_ptr的线程安全问题
  • 六、总结
  • 温馨提示

引言

在 C++ 动态内存管理中,除了 auto_ptrunique_ptr 之外,还有一种智能指针 shared_ptr,它可以让多个指针共享同一个动态资源,并且能够自动释放资源。shared_ptr 通过引用计数的方式来管理内存,能够避免程序中出现悬空指针和内存泄漏等问题。本文将介绍 shared_ptr 的简介和使用方法,并提供一个 C++ 模拟实现,以帮助读者更好地理解其原理和实现。

一、简介

std::shared_ptr 是 C++11 标准库中的一个智能指针,它可以让多个指针共享同一个动态资源,并且能够自动释放资源。shared_ptr 通过引用计数的方式来管理内存,能够避免程序中出现悬空指针和内存泄漏等问题

std::auto_ptrstd::unique_ptr 不同,std::shared_ptr 可以被多个指针所共享。当一个 shared_ptr 被赋值给另一个 shared_ptr 或者被拷贝构造时,它所管理的资源的引用计数会增加。只有在最后一个 shared_ptr 被销毁时,才会释放所管理的资源。这种语义被称为“共享所有权”

🔴std::shared_ptr官方文档

在这里插入图片描述

二、成员函数

为了方便用户使用 shared_ptr 智能指针,shared_ptr<T> 模板类还提供有一些实用的成员方法,它们各自的功能如下表所示

成员方法名功能
operator=()重载赋值号,使得同一类型的 shared_ptr 智能指针可以相互赋值。
operator * ()重载 * 号,获取当前 shared_ptr 智能指针对象指向的数据。
operator->()重载 -> 号,当智能指针指向的数据类型为自定义的结构体时,通过 -> 运算符可以获取其内部的指定成员。
swap()交换 2 个相同类型 shared_ptr 智能指针的内容。
reset()当函数没有实参时,该函数会使当前 shared_ptr 所指堆内存的引用计数减 1,同时将当前对象重置为一个空指针;当为函数传递一个新申请的堆内存时,则调用该函数的 shared_ptr 对象会获得该存储空间的所有权,并且引用计数的初始值为 1。
get()获得 shared_ptr 对象内部包含的普通指针。
use_count()返回同当前 shared_ptr 对象(包括它)指向相同的所有 shared_ptr 对象的数量。
unique()判断当前 shared_ptr 对象指向的堆内存,是否不再有其它 shared_ptr 对象再指向它。
operator bool()判断当前 shared_ptr 对象是否为空智能指针,如果是空指针,返回 false;反之,返回 true

⭕当然除此之外,C++11 标准还支持同一类型的 shared_ptr 对象,或者 shared_ptrnullptr 之间,进行 ==!=<<=>>= 运算。

三、使用示例

下面是一个使用 std::shared_ptr 的示例:

#include <iostream>
#include <memory>int main() {std::shared_ptr<int> sp1(new int(42)); // 创建一个指向整数 42 的 shared_ptrstd::shared_ptr<int> sp2 = sp1; // sp2 和 sp1 现在都指向同一个对象std::cout << *sp1 << " " << *sp2 << std::endl; // 输出结果为 42 42*sp1 = 10;std::cout << *sp1 << " " << *sp2 << std::endl; // 输出结果为 10 10sp1.reset(); // 释放 sp1 的所有权std::cout << *sp2 << std::endl; // 输出结果为 10sp2.reset(); // 释放 sp2 的所有权return 0;
}

在这个示例中,我们首先创建了一个指向整数 42 的 shared_ptr,然后将其赋值给另一个 shared_ptr。由于共享所有权的语义,它们都指向同一个对象。接着,我们修改了 sp1 指向的对象的值,然后释放了 sp1 的所有权,此时 sp2 仍然可以访问该对象。最后,我们释放了 sp2 的所有权,整个示例结束。

四、C++模拟实现

#include <iostream>
#include <mutex>using namespace std;template<class T>
class shared_ptr
{
public:// 构造函数explicit shared_ptr(T* ptr = nullptr): _ptr(ptr), _pcount(new int(1)), _pmtx(new mutex){}// 析构函数~shared_ptr(){Release();}/* 释放资源Release() 方法减少引用计数,并根据引用计数的值来判断是否需要删除指向的堆内存对象和引用计数对象。在操作之前,我们使用互斥量 _pmtx 进行加锁,以保证线程安全。*/void Release(){_pmtx->lock();bool deleteFlag = false;if (--(*_pcount) == 0){if (_ptr){// 删除器进行删除_del(_ptr);}delete _pcount;deleteFlag = true;}_pmtx->unlock();if (deleteFlag){delete _pmtx;}}// 增加引用计数void AddCount(){_pmtx->lock();++(*_pcount);_pmtx->unlock();}// 拷贝构造函数shared_ptr(const shared_ptr<T>& sp): _ptr(sp._ptr), _pcount(sp._pcount), _pmtx(sp._pmtx){AddCount();}// 赋值运算符重载shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){Release();_ptr = sp._ptr;_pcount = sp._pcount;_pmtx = sp._pmtx;AddCount();}return *this;}// operator*() 重载T& operator*(){return *_ptr;}// operator->() 重载T* operator->(){return _ptr;}// get() 方法T* get(){return _ptr;}// use_count() 方法int use_count(){return *_pcount;}// swap() 方法,交换 2 个 shared_ptr 智能指针的内容void swap(shared_ptr<T>& sp) noexcept{std::swap(_ptr, sp._ptr);std::swap(_pcount, sp._pcount);std::swap(_pmtx, sp._pmtx);}// reset() 方法,重置 shared_ptr 智能指针对象void reset(T* ptr = nullptr){// 释放原有资源Release();// 重新赋值_ptr = ptr;_pcount = new int(1);_pmtx = new mutex;}private:T* _ptr;           // 指向堆内存对象的指针int* _pcount;      // 引用计数的指针mutex* _pmtx;      // 保护引用计数的互斥量// 包装器function<void(T*)> _del = [](T* ptr){cout << "lambda delete:" << ptr << endl;delete ptr; };
};

以上代码是一个简化版的 shared_ptr 智能指针模板类的实现。智能指针是 C++ 中的一个重要工具,可以帮助开发者更方便地管理动态内存。在手动管理内存时,很容易出现内存泄漏和悬垂指针等问题,而使用智能指针则可以自动管理对象的生命周期,避免这些问题。

shared_ptr 类实现了一个引用计数机制,它通过维护一个引用计数,来判断指向的堆内存对象是否应该被释放。当有多个 shared_ptr 指向同一个堆内存对象时,引用计数会增加;当 shared_ptr 对象销毁时,引用计数会减少。当引用计数为 0 时,就可以释放堆内存对象了

🚨🚨注意shared_ptr 类是一个简化版实现,可能存在一些问题,不适用于生产环境中。在实际开发中,我们可以使用标准库中的 shared_ptr 类或其他第三方库中的智能指针实现

五、std::shared_ptr的线程安全问题

标准库中的 std::shared_ptr 是线程安全的,可以在多线程环境下使用。它通过使用原子操作和引用计数来实现线程安全

std::shared_ptr 内部,引用计数是一个原子操作,确保多个线程可以安全地对其进行增加和减少操作。当有一个新的 std::shared_ptr 指向同一块堆内存时,引用计数会增加;当某个 std::shared_ptr 对象销毁时,引用计数会减少。只有当引用计数为 0 时,才会释放堆内存。

此外,std::shared_ptr 还使用了原子操作来保证多个线程之间对智能指针对象的访问是互斥的。这意味着,在多线程环境下,不同的线程可以同时拷贝和赋值 std::shared_ptr 对象,而不会出现数据竞争的问题。

🚨🚨注意:虽然 std::shared_ptr 提供了线程安全的引用计数和访问控制,但它本身并不保证所指向的对象是线程安全的。如果多个线程同时访问和修改同一块内存,则需要额外的同步机制来确保线程安全性。

六、总结

shared_ptr 是C++中的智能指针类,通过引用计数机制管理堆内存对象的生命周期,并使用原子操作确保引用计数的线程安全性。它支持拷贝构造和赋值运算符重载,可以安全地共享指向同一块堆内存的对象。此外,shared_ptr提供了方便的访问和操作接口,是一种方便而安全的资源管理工具。

当然,从上述方面来看,shared_ptr 确实是一种非常强大的工具。然而,它也存在一个缺点,即“循环引用问题”。在下一篇文章中,我将介绍与之相关的知识点——weak_ptrweak_ptr 是一种特殊的智能指针,用于解决 shared_ptr 循环引用可能导致的内存泄漏问题。通过引入weak_ptr我们可以打破循环引用,避免内存泄漏,并在需要时安全地访问对象。敬请期待下一篇文章的发布!

温馨提示

感谢您对博主文章的关注与支持!另外,我计划在未来的更新中持续探讨与本文相关的内容,会为您带来更多关于C++以及编程技术问题的深入解析、应用案例和趣味玩法等。请继续关注博主的更新,不要错过任何精彩内容!

再次感谢您的支持和关注。期待与您建立更紧密的互动,共同探索C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!
在这里插入图片描述

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

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

相关文章

钉钉副总裁李智勇:AI超级助理,提升大模型时代生产力

微软比尔盖茨此前曾预言:“五年内&#xff0c;每个人都将拥有AI私人助理Agent&#xff0c;Agent将颠覆软件行业 。” 近日以来&#xff0c;在GPT store正式上线点爆情绪之后&#xff0c;无论国内外&#xff0c;Agent都是创业圈里炙手可热的新贵。一场关于Agent创业比拼大赛&am…

S2-04 ESP-IDF开发 : IIC

IIC IIC(Inter&#xff0d;Integrated Circuit)总线是一种由NXP&#xff08;原PHILIPS&#xff09;公司开发的两线式串行总线&#xff0c;用于连接微控制器及其外围设备。多用于主机和从机在数据量不大且传输距离短的场合下的主从通信&#xff0c;在小数据量场合使用&#xff…

【Linux第二课-权限】操作系统、Linux用户、Linux权限、Linux文件类型、粘滞位

目录 操作系统shell外壳为什么有shell外壳shell外壳是什么shell外壳工作原理 Linux用户root用户与非root用户root用户与普通用户的切换普通用户 --> root用户root用户 --> 普通用户普通用户 --> 普通用户对一条指令提升为root权限进行执行 Linux权限Linux中的权限角色…

解决国内Linux服务器无法使用Github的方法

解决思路&#xff1a;修改Host https://www.ipaddress.com/ 利用上面的网站查询github.com和raw.githubusercontent.com的DNS解析的IP地址 最后&#xff0c;修改服务器的/etc/hosts 添加如下两行&#xff1a; 140.82.112.3 github.com 185.199.108.133 raw.githubuserconte…

树形结构下拉框组件vue-treeselect的使用(安装、模糊匹配、单选、多选、延迟加载、异步搜索等)

一、基本使用流程 首先npm安装依赖 npm install riophae/vue-treeselect --save然后在需要使用的组件中引入 import Treeselect from riophae/vue-treeselect import riophae/vue-treeselect/dist/vue-treeselect.css声明组件 components: { Treeselect }使用 <treesele…

安装布隆过滤器

上传并解压文件解压文件 tar -zxvf RedisBloom-2.2.4.tar.gz 进入解压好的文件 make一下 返回进入conf 配置文件 loadmodule /usr/local/etc/redis/redisbloom.so 粘入 拷贝redisbloom.so到容器 : docker cp redisbloom.so redis:/usr/local/etc/redis 重启redis : docker …

NX二次开发获取圆弧的四个象限点

我是用来用来画水路线框的UF_MODL_ask_curve_points&#xff08;&#xff09;可以按弧长或者弧度获取曲线的等分点&#xff0c;取PI/2的圆弧&#xff0c;即将圆弧四等分&#xff0c;你也可以取任意等分点。 int GetArcPoint(tag_t arc_tag,double point[4][3]) {if(arc_tag0)r…

Ubuntu 20.04安装yum报错:E: Unable to locate package yum

直接上解决方案&#xff01; 1、选择自己对应的版本的源地址 注意需要选择跟系统版本一致的&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu/ 找到Ubuntu点击小问号&#xff0c;进去选择对应的版本&#xff0c;将下面的镜像复制到Linux系统的 /etc/apt/sourc…

vue2 使用pdf.js 实现pdf预览,并可复制文本

需求&#xff1a;pdf预览&#xff0c;并且可以选中pdf的内容进行复制。 在ruoyi的vue前端项目中用到&#xff0c;参考了网上不少文章&#xff0c;因为大部分没给具体的pdf.js版本&#xff0c;导致运行过程中报各种api 错误&#xff0c;经过尝试以下版本可用&#xff0c…

ARM多核调度器DSU

1. 背景 从A75开始&#xff0c;ARM提出了一个新的多核心管理系统单元&#xff0c;叫做DSU(DynamIQ Shared Unit)。DSU的核心功能是控制CPU内核&#xff0c;使其成簇Cluster使用&#xff0c;簇内每一个核心可以单独开关、调整频率/电压&#xff0c;能效表现更佳&#xff0c;甚至…

Docker-Confluence部署记录

启动 docker container run -v $(pwd):/var/atlassian/application-data/confluence/ --nethost -d --nameconfluence_720_20240120 confluence/confluence:7.2.0新建mysql数据库 导入破解包 atlassian-agent 参考-Confluence 破解方式&#xff08;Linux&#xff09; 按流程破…

springcloud OpenFeign服务接口调用

文章目录 代码下载地址OpenFeign简介OpenFeign使用步骤测试 OpenFeign超时控制超时设置&#xff0c;故意设置超时演示出错情况服务提供方8001故意写暂停程序服务消费方80添加超时方法PaymentFeignService服务消费方80添加超时方法OrderFeignController测试YML文件里需要开启Ope…

区间问题笔记

1、k倍区间 #include <iostream> #include<cmath> #include<vector> #include<algorithm> #include<stack> using namespace std;long long sum[100005]; // 前缀和数组 int cnt[100005]; // 记录sum[i]除k得到的余数的个数 int ans;int…

操作系统实验报告

目录 目录 实验一 一、实验结果 实验二 使用信号量实现进程互斥与同步 一、实验结果 1. 使用信号量实现有限缓冲区的生产者和消费者问题 2. 使用信号量实现读进程具有优先权的读者和写者问题 实验三 死锁和预防 一、实验要求 二、实验内容 三、实验结果 实验四 内…

js中高德地图如何自身所在城市

直接上代码 <!doctype html> <html> <head><meta charset"utf-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"initial-scale1.0, user-scalableno, wi…

Linux中的新建用户、切换用户

目录 一、Linux系统中有哪些用户 二、新建普通用户 三、root账号与普通账号的切换 一、Linux系统中有哪些用户 1.root 超级管理员&#xff08;不受权限约束&#xff09; 2.其他用户 普通用户&#xff08;受到权限约束&#xff09; 二、新建普通用户 创建新用户 sudo user…

如何删除Windows密码,看这篇文章就够了

删除Windows帐户的密码一点也不困难。删除密码后,当计算机启动时,你就不再需要登录到Windows。 在你删除密码后,家里或办公室里的任何人都可以完全访问你电脑上的所有内容,所以这样做并不是一件非常注重安全的事情。 ​注意:如果你忘记了密码,那么你将无法使用下面的方…

Oracle 12CR2 RAC部署翻车,bug避坑经历

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

【AI】ChatGPT和文心一言那个更好用

大家好&#xff0c;我是全栈小5&#xff0c;欢迎阅读文章&#xff01; 此篇是【话题达人】序列文章&#xff0c;这一次的话题是《自然语言处理的发展》 文章将以博主的角度进行讲述&#xff0c;理解和水平有限&#xff0c;不足之处&#xff0c;望指正。 目录 背景自我介绍面试题…

git本地分支的合并

目录 第一章、本地分支的切换测试1.1&#xff09;切换之前的master分支下文件内容1.2&#xff09;切换到develop分支后修改文件1.3&#xff09;切回master分支出现报错&#xff1a;1.4&#xff09;报错分析 第二章、解决方式2.1&#xff09;方式1&#xff1a;commit2.2&#xf…