C++的智能指针 RAII

目录

产生原因

RAII思想

C++11的智能指针

智能指针的拷贝与赋值 

 shared_ptr的拷贝构造

shared_ptr的赋值重置

shared_ptr的其它成员函数

weak_ptr

定制删除器

简单实现


产生原因

产生原因:抛异常等原因导致的内存泄漏

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}

void Func()
{
	// 1、如果p1这里new 抛异常会如何?
	// 2、如果p2这里new 抛异常会如何?
	// 3、如果div调用这里又会抛异常会如何?
	int* p1 = new int;
	int* p2 = new int;
	cout << div() << endl;
	delete p1;
	delete p2;
}

int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

RAII思想

基本概念:智能指针(Smart Pointer)是C++中一种用于自动管理动态分配内存的对象,旨在防止内存泄漏和指针悬挂问题

核心理念:RAII,即一种利用对象生命周期来控制程序资源(如内存、网络连接等)的技术

工作原理:使用一份资源时,先用该资源构造一个智能指针,智能指针会保证在指向资源仍存在时始终有效(该资源的释放在智能指针的析构函数中),智能指针是由一个匿名对象构建得,为了能像指针一样使用,智能指针类中还会重载*和-> 

#include<iostream>
using namespace std;

//智能指针类
template<class T>
class SmartPtr
{
public:
	// RAII
	SmartPtr(T* ptr)//接收一份资源的地址
		:_ptr(ptr)//_ptr指向一份资源
	{}

	~SmartPtr()
	{
		cout << "delete:" << _ptr << endl;
		delete _ptr;//智能指针对象释放时才释放_ptr指向的资源
	}

	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}

private:
	T* _ptr;
};

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}

void Func()
{
	//创建一个int* 类型的智能指针
	SmartPtr<int> sp1(new int);
	SmartPtr<int> sp2(new int);
	*sp1 += 10;//像指针一样可以*
	
	SmartPtr<pair<string, int>> sp3(new pair<string, int>);//智能指针sp3指向得是一个pair类型的匿名对象
	sp3->first = "apple";
	sp3->second = 1;//等价于sp3.operator->()->second = 1;
		
	cout << div() << endl;
}

int main()
{
	try
	{
		Func();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}

C++11的智能指针

基本概念:C++98时就已经提供了一个叫auto_ptr的智能指针,但使用该智能指针进行拷贝时,会出现管理权的转移,原对象的资源管理权交给了拷贝得到的新对象,这导致指向原对象的指针变为空指针,再次使用可能会报错;所以C++11在boost库的基础上引入了unique_ptr 和 shared_ptr等新的智能指针类C++11的智能指针均包含在<memory>头文件中,需要显示引用)

智能指针的拷贝与赋值 

1、unique_ptr 类不支持拷贝构造和赋值重载(通过只声明不定义实现,禁止了拷贝构造最好将赋值重载也禁掉,不禁掉则赋值重载是默认提供的会进行浅拷贝),适用于不需要拷贝的场景

2、shared_ptr 类有拷贝构造和赋值重载,并使用引用计数解决释放的问题(不使用静态成员进行计数是因为静态成员记录的是所有与被管理资源同类型的资源被使用的次数,即同类型的两个不同资源new出来的两个智能指针不会使得计数器++,而是重置变为1,当然我们设计得拷贝构造仍会使计数器++,但是new时可能会导计数器重置)

 shared_ptr的拷贝构造

基本概念:智能指针shared_ptr在创建时,除了有指向资源的指针ptr,还有该资源独属得计数器指针int * _count,初始时*(_count) = 1

// 构造函数
shared_ptr(T* ptr = nullptr)
	:_ptr(ptr)
	,_pcount(new int(1))
	{}

// 拷贝构造: sp2(sp1)
shared_ptr(const shared_ptr<T>& sp)
{
	_ptr = sp._ptr;
	_pcount = sp._pcount;

	// 拷贝时++计数
	++(*_pcount);
}

shared_ptr的赋值重置

基本概念:shared_ptr的赋值重载需要考虑无效赋值的情况

//赋值重置, sp1 = sp4
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
	//if (this != &sp),防止自我赋值的情况,基础版
	if (_ptr != sp._ptr)//避免管理统一资源的两个智能指针对象间的互相赋值,升级版
	{
        
		release();//先进行释放函数,防止内存泄漏

		_ptr = sp._ptr;
		_pcount = sp._pcount;

		// 拷贝时++计数
		++(*_pcount);
	}

	return *this;
}

//释放函数
void release()
{
    // 先进行计数器--,如果--后智能指针管理的资源的计数器变为0,就释放指向该资源的指针和计数器指针,然后再进行其它操作
	if (--(*_pcount) == 0)
	{
		cout << "delete:" << _ptr << endl;
		delete _ptr;
		delete _pcount;
	}
}

shared_ptr的其它成员函数

基本概念:shared_ptr的析构函数并会直接将其管理的资源直接释放,还是要调用一个release函数进行--_count判断,即使是shared_ptr析构后,如果之前存在对该资源管理权的拷贝或者赋值,则用于记录该资源的计数器指针仍然不会被销毁,因为在拷贝或赋值时又有一个新的智能指针备份了该资源的信息(计数器指针指向的对象是new出来的int类型的对象除非主动释放,否则不会消失)

//析构函数
~shared_ptr()
{
	// 析构时,--计数,计数减到0,
	release();
}

int use_count()
{
	return *_pcount;
}

// 像指针一样
T& operator*()
{
	return *_ptr;
}

T* operator->()
{
	return _ptr;
}

T* get() const//为别人提供自己的指针,且该函数中不能修改自己的指针
{
	return _ptr;
}

shared_ptr的缺陷

基本概念:两个或多个对象通过 shared_ptr 相互引用,从而形成了一个循环。此时,即使所有外部 shared_ptr 都被销毁,由于循环引用中的 shared_ptr 仍然存在,引用计数永远不会降为零,导致这些对象无法被释放,造成内存泄漏

1、防止循环链表两个的结点因抛异常导致的无法释放,我们使用share_ptr管理这两个结点:

struct ListNode
{
	int _val;
	
    //④使得下面的n1->next = n2之类的操作不会因为双方类型不同导致无法互相赋值
    //struct ListNode* _next;
	//struct ListNode* _prev;
              //|
              //v
    std::shared_ptr<ListNode> _next;
	std::shared_ptr<ListNode> _prev;

	ListNode(int val = 0)
		:_val(val)
	{}
};

int main()
{
	//①ListNode* n1 = new ListNode(10);
	//ListNode* n2 = new ListNode(20);
                   //|
                   //v
    std::shared_ptr<ListNode> n1 = ((new) ListNode(10);
	std::shared_ptr<ListNode> n2 = ((new) ListNode(20);

    //②中间可能出现抛异常    
           //|
           //v
    //不用担心抛异常了

    n1->next = n2;
    n2->prev = n1;

    //④delete n1;
	//delete n2;//此时不需要在这里delete了,在智能指针内部会delete

	return 0;
}

2、但是此时又会出现下面三种情况:

        两个结点互相用自己的智能指针管理对方时,管理两个结点的原始智能指针都析构后,记录两个结点的计数器均为1不会变为0,即两个结点均不会释放,此时出现内存泄漏问题,此时如果想要先释放右结点,那么就会出现以下的循环:

  • 这就是shared_ptr在特殊场景下的缺陷......

weak_ptr

 文档:weak_ptr - C++ Reference (cplusplus.com)

基本概念:为了解决shared_ptr在特殊场景下的缺陷,C++11还引入了weak_ptr,该智能指针不增加引用计数,不管理对象的生命周期

 注意事项:

1、weak_ptr不支持RAII(下面是便于理解的简化实现)

// 不支持RAII,不参与资源管理
template<class T>
class weak_ptr
{
    public:

    weak_ptr()
	:_ptr(nullptr)//将传入的指针直接置空即可,不参与资源的管理
    {}

    weak_ptr(const shared_ptr<T>& sp)
    {
    	_ptr = sp.get();//获取shared_ptr的指针
    }

    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;
};

2、weak_ptr也有use_count函数,用于检测此时与weak_ptr指向同一资源的shared_ptr的个数,如果shared_ptr为0(该资源已经被释放了)则weak_ptr变为野指针,应及时置空

3、weak_ptr的expired函数用于检查weak_ptr是否过期,过期返回真,未过期返回假,相比use_count会更方便

总结:使用智能指针不一定会避免内存泄漏(上述结点时使用的纯shared_ptr),正确的使用智能指针才能避免内存泄漏(完善后的shared_ptr + weak_ptr)

定制删除器

基本概念: C++11并没有像boost库中的那样提供shared_array等用于管理和释放由[ ]创建的多个对象的智能指针,所以在使用shared_ptr管理和释放由[ ]开辟的多个对象时,会因为delete与new的方式不匹配而报错(shared_ptr的new和delete均为(),但如是[],释放时仍为()就可能导致出错)

std::shared_ptr<ListNode> p1(new ListNode(10));//正常情况
std::shared_ptr<ListNode> p1(new ListNode[10]);//有[]的情况,无法正确释放

因此C++11的智能指针还提供了接受自定义删除器的构造方式:

template <class U, class D> shared_ptr (U* p, D del);
  • D del自定义删除器,可以是函数指针、函数对象、Lambda 表达式等

1、自定义删除器是函数对象

template<class T>
struct DeleteArray
{
    void operator()(T* ptr)
    {
        delete[] ptr;
    }
}

std::shared_ptr<ListNode> p2(new ListNode[10],DeleteArray<ListNode>());

2、自定义删除器是Lambda表达式

std::shared_ptr<ListNode> p3(fopen("Test.cpp","r"),[](FILE* ptr){fclose(ptr); });

简单实现

template<class T>
class shared_ptr
{
public:

template<class D>
shared_ptr(T* ptr, D del)//接收自定义删除器的构造函数
	:_ptr(ptr)
	, _pcount(new int(1))
	, _del(del)//新定义一个成员用于存放删除器
    {}

//删除函数
void release()
{
	// 说明最后一个管理对象析构了,可以释放资源了
	if (--(*_pcount) == 0)
	{
		cout << "delete:" << _ptr << endl;
		//delete _ptr;
		_del(_ptr);//将执行资源的指针也给删除器一份,从而使删除器开始运行

		delete _pcount;
	}
}

private:
    D _val;//shared_ptr的官方实现的模板中没有第二个模板参数,所以这种写法是错误的
}

//利用function将删除器的类型进行封装
//删除器的返回类型肯定是void,接收的参数类型肯定是T*
function<void(T*)> _del;
​
//为了防止使用无自定义删除器的构造函数时,没有del的传入导致的_del为空的情况,所以我们要提供一个默认的删除方式:
function<void(T*)> _del = [](T* ptr) {delete ptr; };

~over~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

@ControllerAdvice:你可以没用过,但是不能不了解

1.概述 最近在梳理Spring MVC相关扩展点时发现了ControllerAdvice这个注解&#xff0c;用于定义全局的异常处理、数据绑定、数据预处理等功能。通过使用 ControllerAdvice&#xff0c;可以将一些与控制器相关的通用逻辑提取到单独的类中进行集中管理&#xff0c;从而减少代码重…

前端开发接单公司做到哪些点,客户才愿意把项目包给你。

作为前端外包接单公司&#xff0c;你知道客户选择和你合作都看中哪些因素吗&#xff1f;单纯是价格吗&#xff1f;未必&#xff0c;本位给大家列举7个要素&#xff0c;并对每个要素做了定位&#xff0c;大家查缺补漏吧。 作为前端外包接单公司&#xff0c;要吸引同行客户将前端…

优秀的“抗霾”神器:气膜体育馆—轻空间

随着空气污染问题日益严重&#xff0c;尤其是雾霾天气频发&#xff0c;体育运动的场地环境质量受到越来越多的关注。气膜体育馆作为一种新型的体育场馆解决方案&#xff0c;以其独特的设计和多重优势&#xff0c;成为了优秀的“抗霾”神器。轻空间将深入探讨气膜体育馆的特点和…

pycharm不能安装包的解决方法

一直使用VScode写python&#xff0c;最近使用pycharm&#xff0c;但是pycharm不能安装包&#xff0c;类似这种 后面直接使用ALT F12跳转终端&#xff1a; pip install 需要添加的包 -i https://pypi.tuna.tsinghua.edu.cn/simple不报错了

Gitee 的公钥删不掉

公钥管理里已经没有公钥了&#xff0c; 仓库里还有&#xff0c;这是怎么回事&#xff1f; 这两个好像又没什么关系。 那为啥要搞两处呢&#xff1f; 个人信息里的公钥一直就没有仓库里使用的公钥&#xff0c; 删掉个人信息里的也没什么影响。 在仓库管理页面导入新公钥提示已…

【shell脚本速成】mysql备份脚本

文章目录 案例需求脚本应用场景&#xff1a;解决问题脚本思路实现代码 &#x1f308;你好呀&#xff01;我是 山顶风景独好 &#x1f388;欢迎踏入我的博客世界&#xff0c;能与您在此邂逅&#xff0c;真是缘分使然&#xff01;&#x1f60a; &#x1f338;愿您在此停留的每一刻…

大数据集群数据传输

简单的服务器间的通信示例 netcat&#xff0c;简写为 nc&#xff0c;是 unix 系统下一个强大的命令行网络通信工具&#xff0c;用于在两台主机之间建立 TCP 或者 UDP 连接&#xff0c;并提供丰富的命令进行数据通信。nc 在网络参考模型属于应用层。使用 nc 可以做很多事情&…

探索Elastic Search:强大的开源搜索引擎,详解及使用

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引入 全文搜索属于最常见的需求&#xff0c;开源的 Elasticsearch &#xff08;以下简称 Elastic&#xff09;是目前全文搜索引…

如何使用手机号查快递?2个方法,包裹信息全掌握

无论是网购、亲友间寄送礼物还是工作中的文件传递&#xff0c;快递都扮演着至关重要的角色。然而&#xff0c;有时候我们可能会忘记自己的快递单号&#xff0c;或者在收到快递时没有留意保存相关信息。 这时候&#xff0c;如果能通过手机号查询快递&#xff0c;无疑会大大方便…

【LLM之KG】CoK论文阅读笔记

研究背景 大规模语言模型&#xff08;LLMs&#xff09;在许多自然语言处理&#xff08;NLP&#xff09;任务中取得了显著进展&#xff0c;特别是在零样本/少样本学习&#xff08;In-Context Learning, ICL&#xff09;方面。ICL不需要更新模型参数&#xff0c;只需利用几个标注…

Spire.PDF for .NET【文档操作】演示:设置 PDF 文档的 XMP 元数据

XMP 是一种文件标签技术&#xff0c;可让您在内容创建过程中将元数据嵌入文件本身。借助支持 XMP 的应用程序&#xff0c;您的工作组可以以团队以及软件应用程序、硬件设备甚至文件格式易于理解的格式捕获有关项目的有意义的信息&#xff08;例如标题和说明、可搜索的关键字以及…

无源编缆测尺助力料场实现自动化堆取料作业

随着工业4.0时代的到来&#xff0c;智能化、无人化成为现代工业发展的重要趋势。在港口码头、钢铁冶金、焦化等高耗能行业中&#xff0c;如何实现物料的精准测量与无人化操作&#xff0c;成为企业提高生产效率、降低人工成本的关键。武汉市微深节能科技有限公司凭借其先进的分段…

如何配置taro

文章目录 step1. 全局安装wepacksetp2. 使用npm安装tarostep3. 项目初始化可能出现的问题 使用taro时需要在本地配置好nodejs环境&#xff0c;关于如何配置nodejs可参考我的这篇博文 如何配置nodejs环境 step1. 全局安装wepack 使用指令npm install webpack -g即可 安装完成…

电脑不小心删除的文件怎么恢复?4个必备恢复方法!

“刚刚在对电脑里的某些垃圾文件进行清理时&#xff0c;我一不小心误删了比较重要的数据。这些误删的数据还有机会恢复吗&#xff1f;希望大家帮帮我&#xff0c;非常感谢&#xff01;” 在这个数字化飞速发展的时代&#xff0c;电脑早已成为我们日常生活和工作中不可或缺的一部…

【arm扩容】docker load -i tar包 空间不足

背景&#xff1a; 首先我在/home/nvidia/work下导入了一些镜像源码tar包。然后逐个load进去。当我 load -i dev-aarch64-18.04-20210423_2000.tar包的时候&#xff0c;出现 Error processing tar file(exit status 1): write /9818cf5a7cbd5a828600d9a4d4e62185a7067e2a6f2ee…

如何解决app广告填充率低、广告填充异常,提升广告变现收益?

APP广告变现有助于开发者获得持续的收益来源&#xff0c;由于广告链路的封闭性和复杂化&#xff0c;一旦出现请求配置参数错误、返回广告源信息缺失、素材被拦截等异常&#xff0c;大部分开发者很难及时查清异常情况&#xff0c;导致广告填充率不理想&#xff0c;甚至填充率常常…

KUBIKOS - Cube Monsters

KUBIKOS - Cube Monsters 是一系列 18 个不同的可爱低多边形移动友好怪物角色!每个角色都有自己的动画集。(移动、空闲、攻击、击中、跳跃等)。 +URP支持+18种不同的动物! + 低多边形(400~900个三角形) + 操纵和动画! + 4096x4096 纹理图集 + Mecanim 准备就绪! + 移动…

【第十三课】区域经济可视化表达——符号表达与标注

一、前言 地图最直接的表达就是使用符号表达。使用符号可以把简单的点线面要 素渲染成最直观的地理符号&#xff0c;提高地图的可读性。只要掌握了 ArcGIS 符号制 作的技巧&#xff0c;分析符号并总结出规则&#xff0c;就可以制作符合要求的地图符号。 &#xff08;一&#…

关于正点原子stm32f103精英板v1的stlink通信失败问题解决方案

由于最新的固件不适配&#xff0c;我们要想其工作要下载007的固件。 https://www.st.com/en/development-tools/stsw-link007.html?dlredirect 版本选择最低的。然后选择windows文件夹&#xff0c;更新程序 然后进keil就能正常识别到了

在线装修管理系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;装修队管理&#xff0c;用户管理&#xff0c;装修管理&#xff0c;基础数据管理&#xff0c;论坛管理 前台账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;…