C++智能指针的简单实现,原理及应用

1. 为什么C++引入了智能指针?

在C++中,引入智能指针主要是为了解决原始指针在使用过程中可能出现的内存泄漏问题。内存泄漏是程序在申请内存后,无法释放已分配的内存,导致内存被无效占用,严重时可能导致系统运行缓慢甚至崩溃。

原始指针在使用过程中,如果忘记释放内存,就会导致内存泄漏。为了避免这个问题,C++引入了智能指针。智能指针是一个能够自动管理内存的指针,它会在离开作用域时自动释放内存,从而避免了内存泄漏的问题。

2.智能指针的分类

智能指针主要有以下几种:

  1. std::unique_ptr:独占所有权的智能指针,只能有一个unique_ptr指向某个对象,当unique_ptr被销毁时,它所指向的对象也会被销毁。
  2. std::shared_ptr:共享所有权的智能指针,允许多个shared_ptr指向同一个对象,当最后一个shared_ptr被销毁时,它所指向的对象才会被销毁。
  3. std::weak_ptr:从shared_ptr派生的智能指针,可以与shared_ptr共享所有权,但它不能单独使用,必须配合shared_ptr使用。

通过使用智能指针,可以有效地避免内存泄漏问题,提高程序的稳定性和可靠性

3.简单实现demo用于理解共享指针

我就用在实战中用得最多的共享指针来做个例子,在扩展到其他两个智能指针

一般这种需要用到类模板来适应不同的类型

template <class T>

用到三个成员指针对象,

    int* m_count;       //计数T* m_ptr;           //指向对象std::mutex* m_mutex;//锁
   int* m_count;       //计数,用于计数,计数值到零时释放所有对象T* m_ptr;           //指向对象std::mutex* m_mutex;//锁,用于多线程下的线程安全

提示:面试时如果你说你会C++智能指针,有时候面试官会问你原理是什么,或者你有深入了解过智能指针的源码嘛,实现原理,你能说出来,是个加分项,共享指针的重点在于有一个计数器,理解它怎么计数的很重要

比如我定义了一个类如下

template <class T>
class SharedPointer
{
        typedef SharedPointer<T> self;
};

1. 自定义构造函数,拷贝函数,赋值函数来赋值,在拷贝和赋值函数中让计数器+1

    explicit SharedPointer(T* ptr=nullptr):m_count(new int(1)),m_ptr(ptr){m_mutex = new std::mutex;}    SharedPointer(const self& other) {m_count = other.m_count;m_ptr = other.m_ptr;m_mutex = other.m_mutex;addCount();}SharedPointer& operator=(const self& other){if(m_ptr != other.m_ptr) {m_count = other.m_count;m_ptr = other.m_ptr;m_mutex = other.m_mutex;addCount();}return *this;}

需要重载一些运算符来操作原有对象

    T* Get()const { return m_ptr; }T& operator* () const { return *m_ptr; }T* operator->() const { return m_ptr; }operator bool() const { return m_ptr; }

还有一些其他函数,其中release函数用来减少计数器的,放在析构函数中调用,当计数器count为0时,自动回收资源

    int getCount(){m_mutex->lock();int count = *m_count;m_mutex->unlock();return count;}
private:void addCount(){m_mutex->lock();(*m_count)++;m_mutex->unlock();}void release(){m_mutex->lock();if(--(*m_count)==0) {delete m_ptr;delete m_mutex;delete m_count;qDebug()<<"释放所有资源。。。";}m_mutex->unlock();}

完整代码如下:

#ifndef SHAREDPOINTER_H
#define SHAREDPOINTER_H
#include <mutex>
#include <QtDebug>
/*
在C++中,类模板(class template)和类模板成员函数的定义通常需要放在头文件中。
这是因为编译器在编译时需要看到完整的模板定义,以便实例化模板生成相应的代码。
如果模板的定义放在源文件中,那么在每个使用该模板的源文件中都需要包含该头文件,
否则编译器将无法识别模板。因此,为了简化代码的维护和提高编译效率,通常将模板的定义放在头文件中。
*/template <class T>
class SharedPointer
{typedef SharedPointer<T> self;
public:explicit SharedPointer(T* ptr=nullptr):m_count(new int(1)),m_ptr(ptr){m_mutex = new std::mutex;}explicit SharedPointer(){}~SharedPointer(){   release(); }SharedPointer(const self& other) {m_count = other.m_count;m_ptr = other.m_ptr;m_mutex = other.m_mutex;addCount();}SharedPointer& operator=(const self& other){if(m_ptr != other.m_ptr) {m_count = other.m_count;m_ptr = other.m_ptr;m_mutex = other.m_mutex;addCount();}return *this;}T* Get()const { return m_ptr; }T& operator* () const { return *m_ptr; }T* operator->() const { return m_ptr; }operator bool() const { return m_ptr; }int getCount(){m_mutex->lock();int count = *m_count;m_mutex->unlock();return count;}
private:void addCount(){m_mutex->lock();(*m_count)++;m_mutex->unlock();}void release(){m_mutex->lock();if(--(*m_count)==0) {delete m_ptr;delete m_mutex;delete m_count;qDebug()<<"释放所有资源。。。";}m_mutex->unlock();}
private:int* m_count;       //计数T* m_ptr;           //指向对象std::mutex* m_mutex;//锁};
#endif // SHAREDPOINTER_H

写一段测试程序:

int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);{SharedPointer<test> T1 =  SharedPointer<test>(new test(1));qDebug()<<"T1.Count"<< T1.getCount();{SharedPointer<test> T2 = T1;qDebug()<<"T1.Count"<<T1.getCount();}qDebug()<<"T1.Count"<<T1.getCount();{SharedPointer<test> T3 =  T1;qDebug()<<"T1.Count"<<T1.getCount();}qDebug()<<"T1.Count"<<T1.getCount();}return a.exec();
}

输出如下:

可以看到,当共享指针的对象,每赋值一次,计数+1,离开作用域时,生命周期结束,会调用析构函数,计数-1,放计数=0时,自动释放所有资源

共享指针的原理说完了,在讲讲其他两种指针

4. 其他两种指针

1.弱指针

1.弱指针用来解决共享指针的循环引用的问题,弱指针是共享指针的辅助类,允许共享但不拥有某对象,不会关联对象的引用次数。
2.不能使用运算符*和->直接访问弱指针的引用对象,而是通过Lock函数生成对象的共享指针(可能为空)

3.当拥有该对象的最后一个共享指针失去其所有权时,任何弱指针都会自动变为空

4.可以强转化为强指针,即共享指针

循环引用的例子:

class Node {  
public:  int data;  std::shared_ptr<Node> next;  Node(int data) : data(data), next(nullptr) {}  
};  int main() {  std::shared_ptr<Node> head = std::make_shared<Node>(1);  std::shared_ptr<Node> second = std::make_shared<Node>(2);  std::shared_ptr<Node> third = std::make_shared<Node>(3);  head->next = second;  second->next = third;  third->next = head; // 形成循环引用  // 由于存在循环引用,head、second、third都无法被正确释放  // 内存泄漏发生  return 0;  
}

弱指针解决共享指针的循环引用:

class Node {  
public:  int data;  std::weak_ptr<Node> next;  Node(int data) : data(data), next(nullptr) {}  
};  int main() {  std::shared_ptr<Node> head = std::make_shared<Node>(1);  std::shared_ptr<Node> second = std::make_shared<Node>(2);  std::shared_ptr<Node> third = std::make_shared<Node>(3);  head->next = second;  second->next = third;  third->next = head; // 形成循环引用  // 使用weak_ptr打破循环引用  head = nullptr; // 释放head指针,使其成为弱引用  second = nullptr; // 释放second指针,使其成为弱引用  third = nullptr; // 释放third指针,使其成为弱引用  // 现在head、second、third可以被正确释放,内存泄漏得到解决  return 0;  
}

2. 独占指针

顾名思义,独占式拥有, 确保一个对象和其相应的资源同一时间只被一个 pointer拥有,意味着他没有拷贝构造函数,赋值函数,保证其唯一性

5. 总结

项目中用到最多的就是共享指针了,特别是一个指针对象被多次引用,导致释放时无从下手或者很麻烦时,就要用共享指针来避免内存泄露了,其他两种指针用的较少,用起来也不难,理解就行了

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

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

相关文章

Redis6.0 Client-Side缓存是什么

前言 Redis在其6.0版本中加入了Client-side caching的支持&#xff0c;开启该功能后&#xff0c;Redis可以将指定的key-value缓存在客户端侧&#xff0c;这样当客户端发起请求时&#xff0c;如果客户端侧存在缓存&#xff0c;则无需请求Redis Server端。 Why Client-side Cac…

【每日一题】【12.24】 - 【12.28】

&#x1f525;博客主页&#xff1a; A_SHOWY&#x1f3a5;系列专栏&#xff1a;力扣刷题总结录 数据结构 云计算 数字图像处理 力扣每日一题_ 本周总结&#xff1a;本周的每日一题比较针对于数学问题的一个应用&#xff0c;如二元一次方程组的求解或者数组求和&#xff0c;同…

Baumer工业相机堡盟工业相机如何通过NEOAPI SDK使用UserSet功能保存和载入相机的各类参数(C++)

Baumer工业相机堡盟工业相机如何通过NEOAPI SDK使用UserSet功能保存和载入相机的各类参数&#xff08;C&#xff09; Baumer工业相机Baumer工业相机NEOAPISDK中UserSet的技术背景代码案例分享第一步&#xff1a;保存相机当前参数设置UserSet_Save第二步&#xff1a;载入已经保存…

C++第2关:文件读取和写入

任务描述 题目描述:从文件a.txt中读取三个整数&#xff0c;然后把这三个整数保存到b.txt中&#xff0c;两整数之间一个空格。 相关知识&#xff08;略&#xff09; 编程要求 根据提示&#xff0c;在右侧编辑器Begin-End处补充代码&#xff0c;完成本关要求。 格式如下: 10…

IDEA、VSCode等快速连接Github(Mac版)

问题描述 在本地书写✍️完代码后, 想要git push到Github上面, 出现延迟错误; 导致经常push不上去, 如下图所示; 解决方案 进入电脑终端; 输入下列命令; sudo vim /etc/hosts输入密码; 按下 I 键, 进行编辑操作; 将下列语句复制到空白区, 然后按下esc按键, 然后输入:wq即可…

矿泉水硝酸盐和溴酸盐超标解决工艺

在当今社会&#xff0c;人们对健康和优质生活的追求不断提升&#xff0c;使得瓶装饮用水的安全问题受到了广泛关注。溴酸盐和硝酸盐作为自然水体中常见的物质&#xff0c;若在矿泉水中含量过高&#xff0c;可能会对消费者的健康构成潜在威胁。因此&#xff0c;探究有效去除矿泉…

AR-HUD厂商发力下一代技术方案,vHOE为何赢得高度关注?

作为智能座舱的核心显示交互系统&#xff0c;AR-HUD正处于处于量产爆发前期&#xff0c;同时关于下一代技术方案的比拼也在全面升级。 根据《高工智能汽车研究院》数据显示&#xff0c;2023年1-9月&#xff0c;中国市场&#xff08;不含进出口&#xff09;乘用车前装标配W/AR …

区块链背后的秘密:从交易看故事

作者&#xff1a;shellyfootprint.network 在区块链的世界里&#xff0c;每一笔交易都是一个故事的开始。不只是数字的交换&#xff0c;更是用户行为、信念和决策的体现。 疯狂投机的背后&#xff0c;是短期的逐利还是长期的策略&#xff1f;协议分叉&#xff0c;真的分裂了社…

双向链表基本操作及顺序和链表总结

目录 基本函数实现 链表声明 总的函数实现声明 创建一个节点 初始化链表 打印 尾插 尾删 头插 头删 查找 pos前插入 删除pos位置 销毁链表 顺序表和链表总结 基本函数实现 链表声明 typedef int DLTDataType;typedef struct DListNode {struct DListNode* nex…

POLL机制

文章目录 一、POLL机制1、应用场景2、执行流程 二、程序1、驱动程序2、测试应用程序 三、总结 一、POLL机制 1、应用场景 使用休眠-唤醒的方式等待某个事件发生时&#xff0c;有一个缺点&#xff1a;等待的时间可能很久。我们可以加上一个超时时间&#xff0c;这时就可以使用…

CAN总线应用篇(c语言版)

一.概述 CAN&#xff08;Controller Area Network&#xff09;即控制器局域网&#xff0c;是一种能够实现分布式实时控制的串行通信网络。想到CAN就要想到德国的Bosch公司&#xff0c;因为CAN就是这个公司开发的&#xff08;和Intel&#xff09;CAN有很多优秀的特点&#xff0c…

百度CTO王海峰:文心一言用户规模破1亿

▶ 写在前面▶ 飞桨开发者已达1070万▶ 文心一言用户规模破亿&#xff0c;日提问量快速增长 ▶ 写在前面 “文心一言用户规模突破 1 亿。”12 月 28日&#xff0c;百度首席技术官、深度学习技术及应用国家工程研究中心主任王海峰在第十届 WAVE SUMMIT 深度学习开发者大会上宣布…

Python初学者必须吃透的69个内置函数!

所谓内置函数&#xff0c;就是Python提供的, 可以直接拿来直接用的函数&#xff0c;比如大家熟悉的print&#xff0c;range、input等&#xff0c;也有不是很熟&#xff0c;但是很重要的&#xff0c;如enumerate、zip、join等&#xff0c;Python内置的这些函数非常精巧且强大的&…

外贸网站建站怎么做?海洋建站有哪些步骤?

外贸网站建站需要哪些资料&#xff1f;如何选择外贸建站系统&#xff1f; 外贸企业越来越重视在线业务&#xff0c;而拥有一个专业、高效的外贸网站已经成为成功开展国际贸易的关键一步。海洋建站将为您详细介绍如何进行外贸网站建站&#xff0c;让您的企业在全球市场中脱颖而…

C++ Qt开发:QItemDelegate自定义代理组件

老规矩&#xff0c;首先推荐好书&#xff1a; Qt 是一个跨平台C图形界面开发库&#xff0c;利用Qt可以快速开发跨平台窗体应用程序&#xff0c;在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置&#xff0c;实现图形化开发极大的方便了开发效率&#xff0c;本章将重点介绍…

浏览器Post请求出现413 Request Entity Too Large (Nginx)

环境 操作系统 window server 2016 前端项目 Vue2 Nginx-1.25.3 一、错误信息 前端是vue项目&#xff0c;打包后部署在Nginx上&#xff0c;前端post请求出现Request Entity Too Large错误信息。 ​这种问题一般是请求实体太大&#xff08;包含参数&#xff0c;文件等&#xf…

SpringBoot集成etcd,实现实时监听,实现配置中心

etcd 是一个分布式键值对存储&#xff0c;设计用来可靠而快速的保存关键数据并提供访问。通过分布式锁&#xff0c;leader选举和写屏障(write barriers)来实现可靠的分布式协作。etcd集群是为高可用&#xff0c;持久性数据存储和检索而准备。 以下代码实现的主要业务是&#xf…

复试 || 就业day01(2023.12.27)算法篇

文章目录 前言两数之和存在重复元素 II好数对的数目总持续时间可被 60 整除的歌曲 前言 &#x1f4ab;你好&#xff0c;我是辰chen&#xff0c;本文旨在准备考研复试或就业 &#x1f4ab;文章题目大多来自于 leetcode&#xff0c;当然也可能来自洛谷或其他刷题平台 &#x1f4a…

2023-12-27 Python PC获取鼠标位置,移动鼠标到相应的位置 定时自动模拟鼠标点击,用于简单测试app用

一、核心源码如下&#xff1a; import pyautogui import timepyautogui.moveTo(600, 800) for i in range(20):time.sleep(0.1)x, y pyautogui.position()print("mouse position:", x, y)pyautogui.click()二、定时自动模拟鼠标点击&#xff0c;模拟键盘按键 impo…

C语言-第十七周课堂总结-数组

找出矩阵中最大值所在的位置 程序解析-求矩阵的最大值 源程序段 二维数组 多维数组的空间想象 一维数组&#xff1a;一列长表或一个向量二维数组&#xff1a;一个表格或一个平面矩阵三维数组&#xff1a;三位空间的一个方阵多维数组&#xff1a;多维空间的一个数据矩阵 …