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;同…

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 …

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

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

POLL机制

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

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

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

2019年全国学校POI数据

2019年全国学校POI数据 POI&#xff08;一般作为Point of Interest的缩写&#xff0c;也有Point of Information的说法&#xff09;&#xff0c;通常称作兴趣点&#xff0c;泛指互联网电子地图中的点类数据&#xff0c;基本包含名称、地址、坐标、类别四个属性&#xff1b;在GI…

如何利用树莓派与Nginx结合cpolar内网穿透工具实现公网访问内网web网站

文章目录 1. Nginx安装2. 安装cpolar3.配置域名访问Nginx4. 固定域名访问5. 配置静态站点 安装 Nginx&#xff08;发音为“engine-x”&#xff09;可以将您的树莓派变成一个强大的 Web 服务器&#xff0c;可以用于托管网站或 Web 应用程序。相比其他 Web 服务器&#xff0c;Ngi…

QML —— ProgressBar示例(附完整源码)

示例 - 效果 实例 - 源码 import QtQuick 2.12 import QtQuick.Window 2.12import QtQuick.Layouts 1.12 import QtQuick.Controls 2.5Window {id: rootIdvisible: truewidth: 640height: 480title: qsTr("Hello World")Column{spacing: 40anchors.centerIn: parent…

车路协同中 CUDA 鱼眼相机矫正、检测、追踪

在车路协同中,鱼眼一般用来补充杆件下方的盲区,需要实现目标检测、追踪、定位。在目标追踪任务中,通常的球机或者枪机方案,无法避免人群遮挡的问题,从而导致较高的ID Swich,造成追踪不稳定。但是鱼眼相机的顶视角安装方式,天然缓解了遮挡的问题,从而实现杆件下方的盲区…

携手共进 探索生命|清华大学创融同学会走进生命系 共话细胞科技新未来

携手共进 探索生命&#xff5c;清华大学创融同学会走进生命系 共话细胞科技新未来 探索细胞产业新高度&#xff0c;赋予生命健康更多保障&#xff01;日前&#xff0c;清华大学创融同学会一行莅临全生命周期健康管理中心——生命系参观交流。生命系领导以及全体员工对来访贵宾…

【10】ES6:Promise 对象

一、同步和异步 1、JS 是单线程语言 JavaScript 是一门单线程的语言&#xff0c;因此同一个时间只能做一件事情&#xff0c;这意味着所有任务都需要排队&#xff0c;前一个任务执行完&#xff0c;才会执行下一个任务。但是&#xff0c;如果前一个任务的执行时间很长&#xff…

Python3 基本数据类型

Python 中的变量不需要声明。每个变量在使用前都必须赋值&#xff0c;变量赋值以后该变量才会被创建。 在 Python 中&#xff0c;变量就是变量&#xff0c;它没有类型&#xff0c;我们所说的"类型"是变量所指的内存中对象的类型。 等号&#xff08;&#xff09;用来…