C++_智能指针

文章目录

  • 前言
  • 一、智能指针原理
  • 二、库支持的智能指针类型
    • 1.std::auto_ptr
    • 2.std::unique_ptr
    • 3.std::shared_ptr
    • 4.std::weak_ptr
  • 三、删除器
  • 总结


前言

智能指针是一种采用RAII思想来保护申请内存不被泄露的方式来管理我们申请的内存,对于RAII,我们之前也已经有过接触,在学习异常和guard_mutex都有过接触RAII思想。

今天我们将RAII运用到指针就是智能指针。


一、智能指针原理

template<class T>
class SmartPtr {
public:SmartPtr(T* ptr = nullptr): _ptr(ptr){}~SmartPtr(){if (_ptr){std::cout << "~SmartPtr()" << std::endl;delete _ptr;}}T& operator*() {return *_ptr;}T* operator->() {return _ptr;}
private:T* _ptr;
};int main()
{SmartPtr<int> ip = new int;return 0;
}

运行结果
在这里插入图片描述
就算我们没有在main函数主动调用delete,因为类对象出了生命周期自动调用析构函数的delete,这样保证了我们内存泄露的风险。

二、库支持的智能指针类型

1.std::auto_ptr

C++98版本的库中就提供了auto_ptr的智能指针
auto_ptr是一个失败设计,很多公司明确要求不能使用auto_ptr。

template<class T>
class auto_ptr
{
public:auto_ptr(T* ptr):_ptr(ptr){}auto_ptr(auto_ptr<T>& sp):_ptr(sp._ptr){// 管理权转移sp._ptr = nullptr;}auto_ptr<T>& operator=(auto_ptr<T>& ap){// 检测是否为自己给自己赋值if (this != &ap){// 释放当前对象中资源if (_ptr)delete _ptr;// 转移ap中资源到当前对象中_ptr = ap._ptr;ap._ptr = nulllptr;}return *this;}~auto_ptr(){if (_ptr){cout << "delete:" << _ptr << endl;delete _ptr;}}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};
int main()
{auto_ptr<int> ap1 = new int(1);auto_ptr<int> ap2 = ap1;std::cout << *ap1 << std::endl; // 由于管理权转移,ap1._ptr已经是一个nullptrreturn 0;
}

在这里插入图片描述

auto_ptr 的实现方式乍一看没什么问题,但实际运用上,会里会有一个大坑。
导致的原因就是拷贝构造和赋值重载的管理权转移,有人可能会说,那我在使用了拷贝构造和赋值重载之后不再使用被拷贝和赋值的变量不就可以了,虽说如此,但是对于不太了解auto_ptr的人来说,这里还可以对访问被拷贝和赋值的变量会很坑。

2.std::unique_ptr

基于auto_ptr的不成功,C++11中开始提供更靠谱的unique_ptr。
那么它是否解决了auto_ptr的问题呢?

template<class T>
class unique_ptr
{
public:unique_ptr(T* ptr):_ptr(ptr){}~unique_ptr(){if (_ptr){cout << "delete:" << _ptr << endl;delete _ptr;}}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}unique_ptr(const unique_ptr<T>& sp) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
private:T* _ptr;
};

unique_ptr 则是直接将拷贝构造和赋值重载设置为delete,防止拷贝和赋值。

那有没有支持拷贝和赋值的智能指针呢?

3.std::shared_ptr

C++11中开始提供更靠谱的并且支持拷贝的shared_ptr

shared_ptr是通过计数的方式来做到允许拷贝和赋值并且不会出现auto_ptr的情况。

  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
  2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
template<class T>
class shared_ptr
{
public:shared_ptr(T* ptr):_ptr(ptr), _pcount(new int(1)){}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount){++(*_pcount); //增加计数}~shared_ptr(){if (--(*_pcount) == 0){delete _ptr;delete _pcount;std::cout << "delete _ptr  delete _pcount; " << std::endl;}}shared_ptr<T>& operator= (const shared_ptr<T>& sp){if (sp._ptr == _ptr) return *this;if (--(*_pcount) == 0){delete _ptr;delete _pcount;}_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}private:T* _ptr;int* _pcount;
};int main()
{shared_ptr<int> sp1(new int);shared_ptr<int> sp2(sp1);shared_ptr<int> sp3(sp1);shared_ptr<int> sp4(new int);shared_ptr<int> sp5(sp4);sp1 = sp1;sp1 = sp2;sp1 = sp4;sp2 = sp4;sp3 = sp4;	*sp1 = 2;*sp2 = 3;return 0;
}

运行结果没有问题。

上面的示例代码其实还有问题的,因为是通过计数的方式来保证唯一才释放,我们学习过多线程,我们就能看出,这并不是一个线程安全的类,一旦设计到多线程,同时修改_pcount的数据,就会发生数据紊乱的问题,所以我们就可以添加锁来进行保护。

template<class T>
class shared_ptr
{
public:shared_ptr(T* ptr):_ptr(ptr), _pcount(new int(1)),_pmtx(new std::mutex){}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount), _pmtx(sp._pmtx){AddRef();}~shared_ptr(){Release();}shared_ptr<T>& operator= (const shared_ptr<T>& sp){if (sp._ptr == _ptr) return *this;Release();_ptr = sp._ptr;_pcount = sp._pcount;_pmtx = sp._pmtx;AddRef();return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}int use_count(){return *_pcount;}void AddRef(){_pmtx->lock();++(*_pcount);_pmtx->unlock();}void Release(){_pmtx->lock();bool flag = false;if (--(*_pcount) == 0 && _ptr){std:: cout << "delete:" << _ptr << std::endl;delete _ptr;delete _pcount;flag = true;}_pmtx->unlock();if (flag == true){delete _pmtx;}}private:T* _ptr;int* _pcount;std::mutex* _pmtx;
};struct Date
{int _year = 0;int _month = 0;int _day = 0;
};
void SharePtrFunc(shared_ptr<Date>& sp, size_t n, std::mutex& mtx)
{std::cout << sp.get() << std::endl;for (size_t i = 0; i < n; ++i){// 这里智能指针拷贝会++计数,智能指针析构会--计数,这里是线程安全的。shared_ptr<Date> copy(sp);// 这里智能指针访问管理的资源,不是线程安全的。所以我们看看这些值两个线程++了2n次,但是最终看到的结果,并一定是加了2n{std::unique_lock<std::mutex> lk(mtx);copy->_year++;copy->_month++;copy->_day++;}}
}
int main()
{shared_ptr<Date> p(new Date);std::cout << p.get() << std::endl;const size_t n = 100000;std::mutex mtx;std::thread t1(SharePtrFunc, std::ref(p), n, std::ref(mtx));std::thread t2(SharePtrFunc, std::ref(p), n, std::ref(mtx));t1.join();t2.join();std::cout << p->_year << std::endl;std::cout << p->_month << std::endl;std::cout << p->_day << std::endl;std::cout << p.use_count() << std::endl;return 0;
}

在这里插入图片描述

4.std::weak_ptr

shared_ptr其实也有问题,就比如在双向链表中,在析构时,会出现计数永远无法为1的情况,这就导致无法析构。所以就有了weak_ptr来解决这一问题。

struct ListNode
{
int _data;
shared_ptr<ListNode> _prev;
shared_ptr<ListNode> _next;
~ListNode(){ cout << "~ListNode()" << endl; }
};
int main()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
node1->_next = node2;
node2->_prev = node1;
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
return 0;
}

在这里插入图片描述
解决方案则是使用weak_ptr,因为weak_ptr仅仅只是指向资源,不进行管理。

template<class T>
class weak_ptr  //weak_ptr的特点就是指向资源但是不进行管理
{
public:weak_ptr():_ptr(nullptr){}weak_ptr(const shared_ptr<T>& sp):_ptr(sp.get()){}weak_ptr<T>& operator=(const shared_ptr<T>& sp){_ptr = sp.get();return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};struct ListNode{int _data;weak_ptr<ListNode> _prev;weak_ptr<ListNode> _next;~ListNode() { std::cout << "~ListNode()" << std::endl; }
};
int main()
{shared_ptr<ListNode> node1(new ListNode);shared_ptr<ListNode> node2(new ListNode);std::cout << node1.use_count() << std::endl;std::cout << node2.use_count() << std::endl;node1->_next = node2;node2->_prev = node1;std::cout << node1.use_count() << std::endl;std::cout << node2.use_count() << std::endl;return 0;
}

三、删除器

在上面的示例代码中,我们所演示的智能指针的对象的创建都是使用new 来申请空间, 可是如果我们不使用new 的方式来构造_ptr呢? 我们使用new Data[10] 来申请一个数组空间呢? 我们使用fopen来打开文件,让智能指针来管理我们的文件指针呢? 而这里我们的析构函数不管对于什么情况都是采用delete的方式来释放资源,当类型不匹配时,就会出现问题。

所以就有删除器。

template<class T>
class shared_ptr
{
public:shared_ptr(T* ptr):_ptr(ptr), _pcount(new int(1)), _pmtx(new std::mutex){}template<class D>shared_ptr(T* ptr, D del): _ptr(ptr), _pcount(new int(1)), _pmtx(new std::mutex), _del(del){}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount), _pmtx(sp._pmtx){AddRef();}~shared_ptr(){Release();}shared_ptr<T>& operator= (const shared_ptr<T>& sp){if (sp._ptr == _ptr) return *this;Release();_ptr = sp._ptr;_pcount = sp._pcount;_pmtx = sp._pmtx;AddRef();return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}int use_count(){return *_pcount;}void AddRef(){_pmtx->lock();++(*_pcount);_pmtx->unlock();}void Release(){_pmtx->lock();bool flag = false;if (--(*_pcount) == 0 && _ptr){std::cout << "delete:" << _ptr << std::endl;_del(_ptr);delete _pcount;flag = true;}_pmtx->unlock();if (flag == true){delete _pmtx;}}private:T* _ptr;int* _pcount;std::mutex* _pmtx;std::function<void(T*)> _del;
};// 仿函数的删除器
template<class T>
struct FreeFunc {void operator()(T* ptr){std::cout << "free:" << ptr << std::endl;free(ptr);}
};
template<class T>
struct DeleteArrayFunc {void operator()(T* ptr){std::cout << "delete[]" << ptr << std::endl;delete[] ptr;}
};
int main()
{FreeFunc<int> freeFunc;std::shared_ptr<int> sp1((int*)malloc(4), freeFunc);DeleteArrayFunc<int> deleteArrayFunc;std::shared_ptr<int> sp2((int*)malloc(4), deleteArrayFunc);std::shared_ptr<int> sp3(new int[10], [](int* p) {delete[] p; std::cout << "lambda -> delete[]" << std::endl;});std::shared_ptr<FILE> sp4(fopen("test.txt", "w"), [](FILE* p){fclose(p); std::cout << "lambda -> fclose(p)" << std::endl; });return 0;
}

在这里插入图片描述

总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

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

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

相关文章

LeetCode-热题100:102. 二叉树的层序遍历

题目描述 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a; root [3,9,20,null,null,15,7] 输出&#xff1a; [[3],[9,20],[15,7]] 示例 2&am…

【GEE实践应用】使用MODIS NDVI数据集绘制研究区域每日NDVI序列曲线

// 设置研究区域 var geometry table;// 选择MODIS NDVI 数据集 var modisNDVI ee.ImageCollection(MODIS/006/MOD13A2).filterBounds(geometry).filterDate(2000-01-01, 2023-12-31);// 计算每天的平均 NDVI var dailyMeanNDVI modisNDVI.map(function(image) {var date e…

AndroidStudio AGP 7+, 编译aar并输出到本地仓库

1 编写构建gradle脚本代码 1.1 配置publication和repository 在指定moudle目录下新建名为"maven-publish.gradle"文件&#xff0c;其声明的publication和repository如下所示&#xff1a; apply plugin: maven-publish// This creates a task called publishReleas…

# 从浅入深 学习 SpringCloud 微服务架构(二)模拟微服务环境

从浅入深 学习 SpringCloud 微服务架构&#xff08;二&#xff09;模拟微服务环境&#xff08;1&#xff09; 段子手168 1、打开 idea 创建父工程 创建 artifactId 名为 spring_cloud_demo 的 maven 工程。 --> idea --> File --> New --> Project --> Ma…

java高校办公室行政事务管理系统设计与实现(springboot+mysql源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的闲一品交易平台。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 基于mvc的高校办公室行政…

软件缺陷的生命周期

参考&#xff1a;Defect/Bug Life Cycle in Software Testing 什么是缺陷 软件缺陷一般被我们叫做Bug。对应的软件缺陷&#xff0c;就是我们所测试的程序中存在的无法正常运行或功能有相关缺陷。 IEEE729-1983对缺陷的标准定义&#xff1a; 从产品内部看&#xff0c;缺陷是…

基于快照行情的股票/基金 1分钟 K 线合成指南

1. 概述 由于不同交易所不同资产的交易规则是有差异的&#xff0c;导致不同交易所基于快照行情或逐笔成交合成不同资产1分钟 K 线的计算方法是不同的。 本教程旨在提高 DolphinDB 在具体业务场景下的落地效率&#xff0c;降低 DolphinDB 在实际业务使用中的开发难度。 本教程…

C语言如何使⽤指针?

一、问题 指针变量在初始化以后就可以使⽤和参与操作了&#xff0c;那么就要⽤到对指针变量最常⽤的两个操作符——> * 和 &#xff06; 。 二、解答 这⾥⼜要提到始终贯穿着指针的⼀个符号“ * ”&#xff0c;但是这⾥的“ * ”是作为指针运算符使⽤的&#xff0c;叫做取内…

如何合理利用多个中国大陆小带宽服务器?

我们知道在中国大陆带宽单价非常昂贵&#xff0c;一个1Mbps 带宽的机子一年就得卖好几百人民币&#xff0c;这是不值当的&#xff0c;当然我们可以去低价漂阿里云、腾讯云的轻量服务器&#xff0c;99包年&#xff0c;但是带宽太小很难崩。 所以&#xff0c;我们必须构建一个能够…

linux进阶篇:重定向和管道操作

Linux中的重定向和管道操作 llinux中的三种IO设备&#xff1a; 标准输入&#xff08;STDIN&#xff09;,文件描述符号为&#xff1a;0&#xff0c;默认从键盘获取输入 标准输出&#xff08;STDOUT&#xff09;,文件描述符号位&#xff1a;1&#xff0c;默认输出到显示终端 标准…

5GNR刷题

5G帧结构 5G NR帧结构的基本时间单位是( C ) A) subframe B) slot C) Tc D) symbol 5G无线帧长是多少ms&#xff08;B&#xff09; A) 5 B) 10 C) 20 D) 40 下面哪种子载波间隔是中国移动白皮书中规定必选(B ) A) 15KHz B) 30KHz C) 60KHz D) 120KHz 5G参数集包含哪…

【笔试训练】day5

今天的题&#xff0c;最后一题忘公式了&#xff0c;卡了一会推出来了 1、游游的you 思路&#xff1a; 看清题目意思就行&#xff0c;这里的相邻两个o可以重复算&#xff0c;也就是说&#xff0c;“ooo”算2分。 先算you的得分&#xff0c;再算oo 对了&#xff0c;不开long lo…

24年蓝桥杯java-b组

24年蓝桥杯javaB组 蓝桥杯在昨天考完了&#xff0c;结果很不乐观&#xff0c;哎&#xff0c;还是太笨了&#xff0c;脑子确实转的慢&#xff1b;&#x1f625; 本篇博客中解题思路和代码并不一定完全正确&#xff0c;是我和同学们讨论的解答方法&#xff0c;但并未使用官方题…

【python从入门到精通】-- 第五战:函数大总结

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;python从入门到精通&#xff0c;魔法指针&#xff0c;进阶C&#xff0c;C语言&#xff0c;C语言题集&#xff0c;C语言实现游戏&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持创作博文…

Leo赠书活动-24期 【三大层次学习企业架构框架TOGAF】文末送书

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; 赠书活动专栏 ✨特色专栏&#xff1a;…

自定义Centos的终端的命令提示符

背景 当我们使用终端登陆Centos时&#xff0c;就自动打开了ssh终端。这个终端的命令提示符一般是这样的&#xff1a; 这个以#号结束的一行字&#xff0c;就是我们说的命令提示符了。 这个是腾讯云的服务器的提示符&#xff0c;可以看到主机名是VM-4-7-centos。 但是这个看起…

STL库 —— priority_queue 的编写

目录 一、 优先级队列的介绍 二、优先级队列的使用 2.1 建大堆 less 2.2 建小堆 greater 2.3 详解 greater 与 less 三、 priority_queue 的模拟实现 3.1 编写框架 3.2 编写简单函数 3.2 进堆 向上调整 3.3 出堆 向下调整 四、完整代码 一、 优先级队列的介绍 1.…

web轮播图

思路&#xff1a; 例如&#xff1a;有5张轮播的图片&#xff0c;每张图片的宽度为1024px、高度为512px.那么轮播的窗口大小就应该为一张图片的尺寸&#xff0c;即为&#xff1a;1024512。之后将这5张图片0px水平相接组成一张宽度为&#xff1a;5120px,高度依然为&#xff1a;5…

守望先锋2怎么在steam上玩 守望先锋归来steam下载安装

守望先锋2怎么在steam上玩 守望先锋归来steam下载安装 《守望先锋2》是知名游戏开发商暴雪娱乐开发的团队射击游戏。与第一部相比&#xff0c;守望先锋2加入了更多元素和新特性。游戏设定在未来的世界&#xff0c;玩家可以选择不同的英雄&#xff08;heroes&#xff09;加入战…

python聊天室

python聊天室 文章目录 python聊天室chat_serverchat_client使用方式1.局域网聊天2.公网聊天 下面是一个简单的示例&#xff0c;包含了chat_client.py和chat_server.py的代码。 chat_server chat_server.py监听指定的端口&#xff0c;并接收来自客户端的消息&#xff0c;并将消…