【c++修行之路】智能指针

文章目录

  • 前言
  • 为什么用智能指针
  • 智能指针简单实现
    • unique_ptr
    • shared_ptr
  • 循环引用和weak_ptr的引入
    • 循环引用
    • weak_ptr
  • 定制删除器

前言

大家好久不见,今天来学习有关智能指针的内容~

为什么用智能指针

假如我们有如下场景:

double Div()
{int x, y;cin >> x >> y;if (y == 0)throw "div Exception cause div 0!!!";elsereturn (double)x / (double)y;
}int main()
{int* p1 = new int;int* p2 = new int;Div();delete p1;delete p2;
}

由于p1、p2、都需要释放,因此一旦在p2、p3出现了异常我们要手动释放前面的资源,这样的方式特别麻烦,这里还仅仅只是两个资源,一旦涉及更多new的资源会更麻烦,为了解决这个问题,c++引入了智能指针解决这个问题。

智能指针简单实现

一般而言智能指针要有三个问题:
1、利用对象的生命周期来控制程序资源,RAII的思想。
2、像指针一样使用。
3、考虑拷贝的问题。

unique_ptr

上面的例子中,如果p2、div出现了问题,前面的资源就无法释放,虽然可以通过重新抛出的方式来解决,但让代码可读性变得很差,同时也非常麻烦。使用智能指针可以解决这个问题,下面是一个智能指针的实例:

template<class T>
class SmartPtr
{
public://构造保存指针SmartPtr(T* ptr): _ptr(ptr){}//析构释放资源~SmartPtr(){if(_ptr)delete _ptr;cout << "delete _ptr success !" << endl;}//模拟指针的两个行为T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};

这样完美地达到了控制资源释放的要求,但与此同时也引来了一个新的问题,即原生指针是可以拷贝的,但智能指针显然不可以拷贝,因为这里拷贝我们要求浅拷贝,那样对象在释放时同一份资源就会析构两次,这是非常可怕的。

为了解决这个问题,c++98库在实现auto_ptr的时候,使用了一种叫管理权转移的方式,如下代码,但其他这样效果非常不好。

//拷贝的悬空
SmartPtr(SmartPtr& sp): _ptr(sp._ptr)
{sp._ptr = nullptr;
}

在后来的c++准标准库中,boost设计了一种新的智能指针,在c++11中相当于unique_ptr,该指针直接明令禁止不允许拷贝。

禁止别人拷贝的方式有很多,这里介绍两种:
在c++98中,一般只声明不实现,为了防止有人在类外动手脚,需要再用private封死这两个函数;相比之下c++11就更加简单,只需要写上delete关键字即可。

//c++11拷贝赋值禁止unique_ptr(const unique_ptr<T>& up) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;
//c++98禁止:
private:unique_ptr(const unique_ptr<T>& up);unique_ptr<T>& operator=(const unique_ptr<T>& up);

shared_ptr

可以看出上面解决拷贝问题的方式本质就是规避了这个问题,shared_ptr不同,他允许我们进行拷贝构造。

要解决拷贝问题,实际上就是要控制好对象何时释放资源、释放几次的问题,我们发现引用计数很适合解决这个问题。

template<class T>
class shared_ptr
{
public://构造保存指针shared_ptr(T* ptr): _ptr(ptr), _count(new int(1)), _pmtx(new mutex){}//拷贝shared_ptr(const shared_ptr<T>& sp): _ptr(sp._ptr){if (_ptr != sp._ptr){sp._count++;}}//析构释放资源~shared_ptr(){if ((--(*_count) == 0)){delete _ptr;delete _count;cout << "delete _ptr;" << " delete _count; " << endl;}cout << "delete _ptr success !" << endl;}//模拟指针的两个行为T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr = nullptr;int* _count;mutex* _pmtx;
};

上述代码简单模拟了shared_ptr,但我们发现一旦使用了引用计数,不可避免地会出现线程安全问题,为了解决线程安全的问题,我们要对这些地方加锁。

template<class T>
class shared_ptr
{
public://构造保存指针shared_ptr(T* ptr=nullptr): _ptr(ptr), _count(new int(1)), _pmtx(new mutex){}//	+void AddCount(){_pmtx->lock();++(*_count);_pmtx->unlock();}//	-void Release(){_pmtx->lock();bool deleteFlag = false;if (--(*_count) == 0){delete _ptr;delete _count;cout << "delete " << _ptr << endl;deleteFlag = true;}_pmtx->unlock();if (deleteFlag)delete _pmtx;}//拷贝构造shared_ptr(const shared_ptr<T>& sp): _ptr(sp._ptr), _count(sp._count), _pmtx(sp._pmtx){AddCount();}//赋值shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){//这里是赋值之前的-Release();_ptr = sp._ptr;_count = sp._count;_pmtx = sp._pmtx;//注意这里加就是赋值之后的加了AddCount();}return *this;}//析构释放资源~shared_ptr(){Release();}//模拟指针的两个行为T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}int use_count(){return *_count;}private:T* _ptr;int* _count;mutex* _pmtx;
};

注意,shared_ptr本身是线程安全的,但管理的对象并不是线程安全的,需要加锁保护,在一些极端的场景下还会出现循环引用的问题。

循环引用和weak_ptr的引入

循环引用

struct ListNode
{int _val;nhy::shared_ptr<ListNode> _prev;nhy::shared_ptr<ListNode> _next;~ListNode(){cout << "~ListNode" << endl;}
};

如果链表的两个指针也使用shared_ptr,就会出现如图所示循环引用的问题,不仅仅p指向这个对象,还有一个next或prev也指向这个对象,这样双方会僵持不下,谁都无法释放。
在这里插入图片描述

weak_ptr

要解决这个问题,只需要将不必要的计数功能取消即可,其实weakptr本质就是sharedptr取消了计数功能。

template<class T>
class weak_ptr
{
public:weak_ptr():_ptr(nullptr){}weak_ptr(const shared_ptr<T>& sp):_ptr(sp.get()){}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get(){return _ptr;}private:T* _ptr;
};

定制删除器

可以传入一个对象来管理释放资源。

template<class D>
shared_ptr(T* ptr, D del): _ptr(ptr), _count(new int(1)), _pmtx(new mutex), _del(del)
{}function<void(T*)> _del = [](T* ptr) {cout << "default delete" << endl;delete ptr;
};//  定制删除器 -- 可调用对象
template<class T>
struct DeleteArray
{void operator()(T* ptr){cout << "void operator()(T* ptr)" << endl;delete[] ptr;}
};class Date
{
private:int _year;int _month;int _day;
};void test_delete()
{nhy::shared_ptr<int> spa1(new int[10],DeleteArray<int>());nhy::shared_ptr<Date> spa2(new Date[10]);nhy::shared_ptr<Date> spa3(new Date[10],DeleteArray<Date>());
}

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

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

相关文章

STM32+PWM+输入捕获测频

外部时钟&#xff0c;主频64M 定时器1 通道1发出PWM波 频率1K 定时器2 通道1输入捕获&#xff0c;上升沿触发 串口 /* USER CODE BEGIN 0 */ uint32_t time_up_num0;//上升沿计数 float time_frequency;//频率 /* USER CODE END 0 */ 初始换打开定时器 /* USER CODE BEGIN 2 …

mapBox 绘制多边形无法设置 边框宽度 解决方法

目录 一、问题 二、解决方法 三、总结 tips:如嫌繁琐&#xff0c;直接看有颜色的文字即可&#xff01; 一、问题 1.使用mapBox在地图上绘制点、线、面。绘制多边形的时候发现 直接用 zh(一家提供地图引擎的公司),提供的绘制多边形的方法无法设置边框颜色和边框宽度。很是离…

Linux登录时,下游回显非常慢

目录 问题现象 原因分析 解决方法 源码等资料获取方法 问题现象 登录linux时&#xff0c;远程连接正常&#xff0c;[root...]回显非常慢&#xff0c;在执行脚本时&#xff0c;很容易导致命令下发错乱 原因分析 家目录下的.bash_history文件太大&#xff0c;导致每次登陆时读…

SQLSERVER的truncate和delete有区别吗?

一&#xff1a;背景 1. 讲故事 在面试中我相信有很多朋友会被问到 truncate 和 delete 有什么区别 &#xff0c;这是一个很有意思的话题&#xff0c;本篇我就试着来回答一下&#xff0c;如果下次大家遇到这类问题&#xff0c;我的答案应该可以帮你成功度过吧。 二&#xff1…

特斯拉降价阴影下,智己如何「登高」?

作者 | 刘然 来源 | 洞见新研社 都说背靠大树好乘凉&#xff0c;但背靠上汽集团的智己汽车&#xff0c;反而水深火热。 2021年&#xff0c;在智己正式向外界公布了“豪华纯电智能轿车”智己L7之后&#xff0c;其CEO刘涛曾放出豪言&#xff1a;“我们在未来的很多年后再回顾今…

【TI毫米波雷达笔记】DCA1000EVM+mmWave Studio数据采集的MIMO模式设置(多天线发射工作模式)

【TI毫米波雷达笔记】DCA1000EVMmmWave Studio数据采集的MIMO模式设置&#xff08;多天线发射工作模式&#xff09; 以IWR6843AOP为例 其为3发4收的雷达 MIMO模式有两种 TDM-MIMO和BPM-MIMO TDM-MIMO模式&#xff08;时分复用&#xff09; TDM-MIMO模式是最简单和常用的MIM…

【机器学习】特征降维 - 方差选择法VarianceThreshold

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;对网络安全感兴趣的小伙伴可以关注专栏《网络安全入门到精通》 方差选择法 一、方差科普二、方差选择API三、获取数…

Slam十四讲之第一讲和第二讲,实践编程基础

目录 1.镜像寻找①方法1&#xff1a;百度网盘下载②方法2&#xff1a;在开源镜像网站上下载&#xff0c;③方法3&#xff1a;直接在Ubuntu官网下载 2 在VMware中创建虚拟机并安装Ubuntu18.043 安装VMware Tools4 初始系统中&#xff0c;部分软件的安装4.1 gcc 安装4.2 g安装4.3…

github搜索案例

目录结构 public/index.html <!DOCTYPE html> <html lang""><head><meta charset"utf-8"><!-- 针对IE浏览器的一个特殊配置&#xff0c;含义是让IE浏览器以最高的渲染级别渲染页面 --><meta http-equiv"X-UA-Comp…

阿里云配置端口安全组策略

文章目录 为何配置安全组安全组设置安全组应用到实例中 为何配置安全组 nginx正确配置了83端口&#xff0c;却无法访问资源&#xff0c;报502错误&#xff0c;这大概就是服务器的安全策略原因 安全组设置 安全组配置地址 安全组应用到实例中 配置地址

【设计模式】23种设计模式——工厂模式(原理讲解+应用场景介绍+案例介绍+Java代码实现)

工厂模式 需求了解 看一个披萨的项目&#xff1a;要便于披萨种类的扩展&#xff0c;要便于维护 披萨的种类很多(比如 GreekPizz、CheesePizz 等)披萨的制作有 prepare&#xff08;准备材料&#xff09;,bake&#xff08;烘焙&#xff09;,cut&#xff08;切割&#xff09;,b…

element ui 导入模块的封装

导入组件的封装 <template><Modal :visible"visible" title"导入" onSave"onSave" onCancal"closeDialog"><template #default><el-upload ref"upload" class"upload-demo"action"ht…