【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.hqwc.cn/news/417347.html

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

相关文章

伊恩·斯图尔特《改变世界的17个方程》傅里叶变换笔记

主要是课堂的补充&#xff08;yysy&#xff0c;我觉得课堂的教育模式真有够无聊的&#xff0c;PPT、写作业、考试&#xff0c;感受不到知识的魅力。 它告诉我们什么&#xff1f; 空间和时间中的任何模式都可以被看作不同频率的正弦模式的叠加。 为什么重要&#xff1f; 频率分量…

投标文件-优化--word快捷键设置

大纲导航快捷键的设置 “导航窗格”功能是指的如上图所示的功能&#xff0c;在写标书的时候&#xff0c;时刻需要关注你的目录大纲级别是否设置正确&#xff0c;文章层级目录是否正确&#xff0c;每次都是需要“视图--导航窗格”点&#xff0c;把人烦的不行&#xff0c;如何优化…

WordPress怎么去除jquery和CSS静态文件链接中的版本号?附2种方法

我们很多WordPress网站默认情况下所加载的jquery和CSS静态文件链接中都会带有相应的版本号&#xff0c;比如boke112百科使用的YIA主题&#xff0c;加载CSS文件时就会在链接地址后面加上?ver2.7&#xff0c;即是style.css?ver2.7 除了CSS文件会加上版本号外&#xff0c;加载主…

含并行连结的网络(GoogLeNet)

目录 1.GoogLeNet 2.代码 1.GoogLeNet inception不改变高宽&#xff0c;只改变通道数。GoogLeNet也大量使用1*1卷积&#xff0c;把它当作全连接用。 V3耗内存比较多&#xff0c;计算比较慢&#xff0c;但是精度比较准确。 2.代码 import torch from torch import nn from t…

探索设计模式的魅力:一篇文章让你彻底搞懂建造者模式

建造者模式&#xff08;Builder Pattern&#xff09;是一种创建型设计模式&#xff0c;旨在将一个复杂对象的创建过程与其表示分离&#xff0c;使得同样的构建过程可以创建不同的表示形式。 主要角色&#xff1a; 产品&#xff08;Product&#xff09;&#xff1a;表示正在构建…

vue2+webpack升级vue3+vite,报错Cannot read properties of null (reading ‘isCE‘)

同学们可以私信我加入学习群&#xff01; 正文开始 前言问题分析解决总结 前言 系列文章&#xff1a;vue2webpack升级vue3vite&#xff0c;修改插件兼容性bug 前面的文章主要是介绍&#xff0c;在升级初始阶段遇到的一些显而易见的兼容性问题和bug。随着项目迭代的不断深入&a…

从数据角度分析年龄与NBA球员赛场表现的关系【数据分析项目分享】

好久不见朋友们&#xff0c;今天给大家分享一个我自己很感兴趣的话题分析——NBA球员表现跟年龄关系到底大不大&#xff1f;数据来源于Kaggle&#xff0c;感兴趣的朋友可以点赞评论留言&#xff0c;我会将数据同代码一起发送给你。 目录 NBA球员表现的探索性数据分析导入Python…

mysql中的联合索引为什么要遵循最佳左前缀法则?

mysql中的联合索引为什么要遵循最佳左前缀法则&#xff1f; 一、什么是联合索引二、联合索引的优化1.常规的写法&#xff08;回表查询&#xff09;2.优化写法&#xff08;索引覆盖&#xff09; 三、 为什么要遵循最佳左前缀法则&#xff1f;1.如下查询语句可以使用到索引&#…

100天精通鸿蒙从入门到跳槽——第8天:TypeScript 知识储备:泛型

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通Golang》…

k8s---配置资源管理

目录 配置资源管理的方式 secret pod如何来引用secret&#xff1f;&#xff1f;&#xff1f; 陈述式创建&#xff1a; 声明式创建 Secret创建加密文件 使用token挂载 环境变量使用 docker-registry ConfigMap 陈述式 热更新 总结&#xff1a; 配置资源管理的方式 …

开发知识点-JAVA-springboot

springboot springbootConfiguration注解的底层核心原理Bean注解的底层核心原理 springboot Configuration注解的底层核心原理 https://www.bilibili.com/video/BV1rq4y1E7gK/?spm_id_from333.999.0.0&vd_sourcef21773b7086456ae21a58a6cc59023be spring.io 全家桶 24…

ETL概念

ETL ETLELT 技术原理ETL 模式应用场景常见工具ETL未来发展方向 ETL 在BI项目中ETL会花掉整个项目至少1/3的时间&#xff0c; ETL设计的好坏直接关接到BI项目的成败。ETL(Extract-Transform-Load) : 用来描述将数据从来源端经过抽取&#xff08;extract&#xff09;、转换&…