C++_智能指针

news2024/11/16 16:43:03

文章目录

  • 前言
  • 一、智能指针原理
  • 二、库支持的智能指针类型
    • 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已经是一个nullptr
	return 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.coloradmin.cn/o/1605646.html

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

相关文章

这是刚发布的人形机器人?不,分明是《午夜凶铃》现实版

波士顿动力公司大名鼎鼎的人形机器人Atlas&#xff0c;你一定见识过吧。 Atlas可以像人一样行走、奔跑和攀爬 | 波士顿动力公司 这款用液压系统打造的机器人产品&#xff0c;经过十多年的调试升级&#xff0c;才终于拥有了人类一般灵活的身手。在波士顿动力公司历年来放出的视频…

OpenHarmony UI开发-ohos-svg

简介 ohos-svg是一个SVG图片的解析器和渲染器&#xff0c;解析SVG图片并渲染到页面上。它支持大部分 SVG 1.1 规范&#xff0c;包括基本形状、路径、文本、样式和渐变,它能够渲染大多数标准的 SVG 图像。ohos-svg的优点是性能好、内存占用低。 效果展示 SVG图片解析并绘制: …

第七周学习笔记DAY.4-方法重写与多态

学完本次课程后&#xff0c;你能够&#xff1a; 实现方法重写 深入理解继承相关概念 了解Object类 会使用重写实现多态机制 会使用instanceof运算符 会使用向上转型 会使用向下转型 什么是方法重写 方法的重写或方法的覆盖&#xff08;overriding&#xff09; 1.子类根据…

【STM32CubeIDE 1.15.0】汉化包带路径配置过程

一、IDE软件下载 二、汉化版包路径 三、IDE软件板载汉化包 一、IDE软件下载 ST官网IDE下载链接 二、汉化版包路径 https://mirrors.ustc.edu.cn/eclipse/technology/babel/update-site/ 找不到就到.cn后面一级一级进 三、IDE软件板载汉化包 https://mirrors.ustc.edu…

Jmeter 压测-Jprofiler定位接口相应时间长

1、环境准备 执行压测脚本&#xff0c;分析该接口tps很低&#xff0c;响应时间很长 高频接口在100ms以内&#xff0c;普通接口在200ms以内 2、JProfiler分析响应时间长的方法 ①JProfiler录制数据 压测脚本&#xff0c;执行1-3分钟即可 ②分析接口相应时间长的方法 通过Me…

Django之rest_framework(四)

扩展的视图类介绍 rest_framework提供了几种后端视图(对数据资源进行增删改查)处理流程的实现,如果需要编写的视图属于这几种,则视图可以通过继承相应的扩展类来复用代码,减少自己编写的代码量 官网:3 - Class based views - Django REST framework rest_framework.mixi…

cobaltstrike 流量隐藏

云函数 新建一个云函数&#xff0c;在代码位置进行修改 首先导入 yisiwei.zip 的云函数包 PYTHON # -*- coding: utf8 -*- import json, requests, base64def main_handler(event, context):C2 https://49.xx.xx.xx # 这里可以使用 HTTP、HTTPS~下角标~ path event[path]h…

在Windows 11/10/8中打开计算机管理的几种方法,总有一种适合你

序言 计算机管理是Windows中一个功能强大的工具,允许你管理和监视计算机系统的各个方面。使用“计算机管理”,你可以快速访问“设备管理器”、“磁盘管理”、“本地用户管理”等。本文将向你展示如何在Windows 11/10/8中打开“计算机管理器”。 网上有很多方法可以打开计算…

Spring Security详细学习第一篇

Spring Security 前言Spring Security入门编辑Spring Security底层原理UserDetailsService接口PasswordEncoder接口 认证登录校验密码加密存储退出登录 前言 本文是作者学习三更老师的Spring Security课程所记录的学习心得和笔记知识&#xff0c;希望能帮助到大家 Spring Sec…

单分支:if语句

示例&#xff1a; /*** brief how about if? show you here.* author wenxuanpei* email 15873152445163.com(query for any question here)*/ #define _CRT_SECURE_NO_WARNINGS//support c-library in Microsoft-Visual-Studio #include <stdio.h>#define if_state…

C语言学习/复习23---

一、数据的存储 二、数据类型的介绍 三、整型在内存中的存储 将原码转换为补码。如果数是正数&#xff0c;则补码与原码相同&#xff1b;如果数是负数&#xff0c;则先将原码按位取反&#xff0c;然后加1。将补码转换原补码。如果数是正数&#xff0c;则补码与原码相同&#x…

【笔试强训】双指针的思想!

1.数组中字符串的最小距离 题目链接 解题思路&#xff1a; 小技巧 ✌&#xff1a;标记两个字符串是否被找到&#xff0c;每次找到一个字符串就更新一次答案来保证找到的是最小距离。 实现代码&#xff1a; #include <iostream> using namespace std;int main() {in…

快手本地生活服务商系统怎么操作?

当下&#xff0c;抖音和快手两大短视频巨头都已开始布局本地生活服务&#xff0c;想要在这一板块争得一席之地。而这也很多普通人看到了机遇&#xff0c;选择成为抖音和快手的本地生活服务商&#xff0c;通过将商家引进平台&#xff0c;并向其提供代运营服务&#xff0c;而成功…

截图快捷键失效的解决方法 _ 统信UOS _ 麒麟KOS _ 中科方德NFS

原文链接&#xff1a;截图快捷键失效的解决方法 | 统信UOS | 麒麟KOS | 中科方德NFS Hello&#xff0c;大家好啊&#xff01;在日常使用计算机时&#xff0c;截图功能是我们经常需要用到的一个实用工具&#xff0c;它可以帮助我们快速保存屏幕上的信息&#xff0c;用于报告错误…

恭喜上岸的准研究生们,入学后还有这些奖学金

很多学校都开设了研究生的新生奖学金&#xff0c;有些学校是不分学校等级的全覆盖&#xff0c;比如北京科技大学前两年给研一新生每人发1万。 一般来说&#xff0c;新生奖学金的等级划分就是按考研成绩&#xff0c;所以大家一定要尽可能的考高的分数&#xff0c;不仅仅对评奖学…

云HIS医院管理系统源码 SaaS模式 B/S架构 基于云计算技术

一、系统概述 云HIS系统源码是一款满足基层医院各类业务需要的健康云产品。该系统能帮助基层医院完成日常各类业务&#xff0c;提供病患预约挂号支持、收费管理、病患问诊、电子病历、开药发药、住院检查、会员管理、财务管理、统计查询、医生工作站和护士工作站等一系列常规功…

累积分布函数图(CDF)的介绍、matlab的CDF图绘制方法(附源代码)

在对比如下两个误差的时候&#xff0c;怎么直观地分辨出来谁的误差更低一点&#xff1f;&#xff1a; 通过这种误差时序图往往不容易看出来。 但是如果使用CDF图像&#xff0c;以误差绝对值作为横轴&#xff0c;以横轴所示误差对应的累积概率为纵轴&#xff0c;绘制曲线图&am…

gitlab(docker)安装及使用

GitLab GitLab 是一个用于仓库管理系统的开源项目&#xff0c;使用Git作为代码管理工具&#xff0c;并在此基础上搭建起来的Web服务。 下载(docker) 查询docker镜像gitlab-ce gitlab-ce是它的社区版 [rootlocalhost ~]# docker search gitlab-ce NAME …

Xshell和XFtp下载和使用

Xshell和XFtp下载和使用 最好是官网直接下载。 链接: Xshell官网 Xshell官网最近出了免费个人使用版&#xff0c;但是我直接下载的话感觉非常非常慢&#xff0c;或许挂个梯子会好的多。看到图片的红色字没&#xff0c;可能被骗的人比较多。运行之前的Xshell会显示需要最新版的软…

python/pygame 挑战魂斗罗 笔记(二)

一、建立地面碰撞体&#xff1a; 现在主角Bill能够站立在游戏地图的地面&#xff0c;是因为我们初始化的时候把Bill的位置固定了self.rect.y 250。而不是真正的站在地图的地面上。 背景地图是一个完整的地图&#xff0c;没有地面、台阶的概念&#xff0c;就无法通过碰撞检测来…