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

news2024/9/22 5:42:46

在这里插入图片描述

阅读导航

  • 引言
  • 一、简介
  • 二、成员函数
  • 三、使用示例
  • 四、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_ptr
    std::shared_ptr<int> sp2 = sp1; // sp2 和 sp1 现在都指向同一个对象
    std::cout << *sp1 << " " << *sp2 << std::endl; // 输出结果为 42 42
    *sp1 = 10;
    std::cout << *sp1 << " " << *sp2 << std::endl; // 输出结果为 10 10
    sp1.reset(); // 释放 sp1 的所有权
    std::cout << *sp2 << std::endl; // 输出结果为 10
    sp2.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.coloradmin.cn/o/1398394.html

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

相关文章

关于大模型学习中遇到的3

来源&#xff1a;网络 Embedding模型 随着大型语言模型的发展&#xff0c;以ChatGPT为首&#xff0c;涌现了诸如ChatPDF、BingGPT、NotionAI等多种多样的应用。公众大量地将目光聚焦于生成模型的进展之快&#xff0c;却少有关注支撑许多大型语言模型应用落地的必不可少的Embed…

STM32407用汇顶的GT911触摸芯片调试实盘

这个配置很关键 代码 #include "stm32f4xx.h" #include "GT9147.h" #include "Touch.h" #include "C_Touch_I2C.h" #include "usart.h" #include "delay.h" #include "LCD.h" #incl…

HarmonyOS 页面跳转控制整个界面的转场动画

好 本文 我们来说 页面间的转场动画 就是 第一个界面到另一个界面 第一个界面的退场和第二个界面的进场效果 首先 我这里 创建了两个页面文件 Index.ets和AppView.ets index组件 编写代码如下 import router from "ohos.router" Entry Component struct Index {b…

视频监控需求记录

记录一下最近要做的需求&#xff0c;我个人任务还是稍微比较复杂的 需求&#xff1a;需要实现一个视频实时监控、视频回放、视频设备管理&#xff0c;以上都是与组织架构有关 大概的界面长这个样子 听着需求好像很简单&#xff0c;但是~我们需要在一个界面上显示两个厂商的视…

STM32标准库开发——串口发送/单字节接收

USART基本结构 串口发送信息 启动串口一的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);初始化对应串口一的时钟&#xff0c;引脚&#xff0c;将TX引脚设置为复用推挽输出。 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); GPIO_InitTypeDef GPIO_In…

我们应该了解的⽤户画像

当我们谈⽤户画像时&#xff0c;到底在谈什么 对于互联⽹公司来说&#xff0c;企业的增⻓、内容、活动、产品等⼯作基本上都是围绕着“⽤户”来做的&#xff0c;可以说都是在做“⽤户运营”这个⼯作&#xff0c;⽽⽤户画像是⽤户运营⼯作中⾮常重要的⼀环 ⽤户画像的主要特征是…

Linux命令手册

简介 Multics&#xff08;大而全&#xff09;项目失败&#xff0c;吸取教训启动Unix&#xff08;小而精&#xff09;&#xff0c;Linus Benedict Torvalds受Unix启发开发初始版本Linux内核&#xff0c;Git也由其开发&#xff0c;目的是为了更好的管理Linux内核开发。Unix是商业…

Windows如何部署TortoiseSVN客户端

文章目录 前言1. TortoiseSVN 客户端下载安装2. 创建检出文件夹3. 创建与提交文件4. 公网访问测试 前言 TortoiseSVN是一个开源的版本控制系统&#xff0c;它与Apache Subversion&#xff08;SVN&#xff09;集成在一起&#xff0c;提供了一个用户友好的界面&#xff0c;方便用…

专业130+总分380+哈尔滨工程大学810信号与系统考研经验水声电子信息与通信

今年专业课810信号与系统130&#xff0c;总分380顺利考上哈尔滨工程大学&#xff0c;一年的努力终于换来最后的录取&#xff0c;期中复习有得有失&#xff0c;以下总结一下自己的复习经历&#xff0c;希望对大家有帮助&#xff0c;天道酬勤&#xff0c;加油&#xff01;专业课&…

Java找二叉树的公共祖先

描述&#xff1a; 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&#xff08;一个节…

[AI]文心一言出圈的同时,NLP处理下的ChatGPT-4.5最新资讯

前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家&#xff1a;https://www.captainbed.cn/z ChatGPT体验地址 文章目录 前言4.5key价格泄漏ChatGPT4.0使用地址ChatGPT正确打开方式最新功能语音助手存档…

【C语言】编译和链接深度剖析

文章目录 &#x1f4dd;前言&#x1f320; 翻译环境和运行环境&#x1f309;翻译环境 &#x1f320;预处理&#xff08;预编译&#xff09;&#x1f309;编译 &#x1f320;词法分析&#x1f320;语法分析 &#x1f309;语义分析&#x1f320;汇编 &#x1f309; 链接&#x1f…

动态闪图怎么在线合成?仅需三秒在线合成

GIF闪图是一种常见的动态图像格式&#xff0c;它由多个静态图像帧组成&#xff0c;以连续的方式播放&#xff0c;形成动画效果。每个图像帧都可以包含不同的颜色和透明度&#xff0c;因此GIF闪图通常用于展示简单的动画、表情符号或者短视频片段。这种格式在网络上广泛应用&…

论rtp协议的重要性

rtp ps流工具 rtp 协议&#xff0c;实时传输协议&#xff0c;为什么这么重要&#xff0c;可以这么说&#xff0c;几乎所有的标准协议都是国外创造的&#xff0c;感叹一下&#xff0c;例如rtsp协议&#xff0c;sip协议&#xff0c;webrtc&#xff0c;都是以rtp协议为基础&#…

C++中的static(静态)

2014年1月19日 内容整理自The Cherno:C系列 2014年1月20日 内容整理自《程序设计教程&#xff1a;用C语言编程 第三版》 陈家骏 郑滔 -----------------------------------------------------------------------------------------------------------------------------…

【RT-DETR有效改进】利用MobileNetV3替换Backbone(轻量化网络结构,提点)

前言 大家好&#xff0c;这里是RT-DETR有效涨点专栏。 本专栏的内容为根据ultralytics版本的RT-DETR进行改进&#xff0c;内容持续更新&#xff0c;每周更新文章数量3-10篇。 专栏以ResNet18、ResNet50为基础修改版本&#xff0c;同时修改内容也支持ResNet32、ResNet101和PP…

TEE2024大湾区进出口贸易博览会

TEE2024大湾区进出口贸易博览会 INTE 2024RNATIONAL TRADE E-COMMERCE EXPO 时间&#xff1a;2024年08月11--13日 地点&#xff1a;深圳福田会展中心 联合主办&#xff1a; 深圳市电子商务协会 深圳市跨境电子商务行业发展促进会 广东进出口商会 广东省国牌出海电子商务…

根据IP查找城市 - 华为OD统一考试

OD统一考试 题解&#xff1a; Java / Python / C 题目描述 某业务需要根据终端的IP地址获取该终端归属的城市&#xff0c;可以根据公开的IP地址池信息查询归属城市。 地址池格式如下&#xff1a; 城市名起始IP&#xff0c;结束IP 起始和结束地址按照英文逗号分隔&#xff0…

链表回文结构

链表回文结构 编写一个函数&#xff0c;检查输入的链表是否是回文的。 示例 1&#xff1a; 输入&#xff1a; 1->2 输出&#xff1a; false 示例 2&#xff1a; 输入&#xff1a; 1->2->2->1 输出&#xff1a; true 链表的回文结构&#xff0c;应该先找到中间节…

yum配置文件及NFS共享

一 yum配置文件及命令 1 /etc/yum.conf //主配置文件 2 /etc/yum.repos.d/*.repo //yum仓库文件位置 写错一个字母就不行&#xff0c;可以ping www.google.com 测试网络 3 /var/log/yum.log //日志文件 二 yum命令 1 [rootlocalhost ~…