C++的智能指针 RAII

news2024/11/28 14:38:12

目录

产生原因

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~

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

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

相关文章

在Ubuntu系统中部署Java及Spring Boot开发环境

选择Java及Spring Boot构建Web服务具有显著优势&#xff0c;Java的跨平台兼容性保证了服务可在不同操作系统上顺畅运行&#xff0c;而Spring Boot的成熟框架则大大简化了开发流程&#xff0c;减少了繁琐配置。此外&#xff0c;强大的社区支持、易于维护与扩展的特性、优异的性能…

a-table 根据数据自动进行 行合并

<template><div class"chat_query_result"><button click"temp">点击</button><a-table :columns"columns" :data-source"data" bordered></a-table></div> </template><script&g…

计算机毕业设计hadoop+spark+hive游戏推荐系统 游戏数据分析可视化大屏 steam游戏爬虫 游戏大数据 大数据毕业设计 机器学习 知识图谱

游戏推荐系统开题报告 一、引言 随着信息技术和网络技术的飞速发展&#xff0c;电子游戏已成为人们日常生活中不可或缺的一部分。然而&#xff0c;面对海量的游戏资源&#xff0c;用户往往难以找到适合自己的游戏。因此&#xff0c;构建一个高效、准确的游戏推荐系统显得尤为…

C++ | Leetcode C++题解之第171题Excel表列序号

题目&#xff1a; 题解&#xff1a; class Solution { public:int titleToNumber(string columnTitle) {int number 0;long multiple 1;for (int i columnTitle.size() - 1; i > 0; i--) {int k columnTitle[i] - A 1;number k * multiple;multiple * 26;}return num…

万界星空科技MES系统中的仓库管理功能

制造执行系统&#xff08;Manufacturing Execution System&#xff0c;简称MES&#xff09;作为一种面向车间生产调度的管理信息系统&#xff0c;被广泛应用在车间作业调度和控制管理系统中&#xff0c;它以实现车间生产调度最优化为目标。同时&#xff0c;MES作为衔接ERP&…

Scikit-Learn梯度提升决策树(GBDT)

Scikit-Learn梯度提升决策树 1、梯度提升决策树(GBDT)1.1、Boosting方法1.2、GBDT的原理1.3、GBDT回归的损失函数1.4、梯度下降与梯度提升1.5、随机森林与GBDT1.6、GBDT的优缺点2、Scikit-Learn梯度提升决策树(GBDT)2.1、Scikit-Learn GBDT回归2.1.1、Scikit-Learn GBDT回归…

阿里云上构建_VPC专有网络_子网划分_原理说明_创建_释放---分布式云原生部署架构搭建006

可以看到有这种子网掩码计算工具可以使用 可以看到这个是,我们设计的一个子网 192.168.0.0/16 可以看到地址是 从192.168.0.1 到 192.168.255.254 有了子网,我们去看,可以看到在阿里云的管理后台,就有个 创建交换机.可以看到 然后指定这个交换机的网段 这里我们指定了192…

编写水文专业串口通讯软件的开发经历

编写水文专业串口通讯软件的开发经历 一、关于开发 YAC9900 水位雨量 RTU 通讯软件二、软件开发遇到的问题和困难1、开发架构的适应2、开发语言的学习3、.net core 8 架构中串口构建的难点4、YAC9900 水位雨量 RTU 通讯软件开发中的 UI 冻结 三、发现问题解决问题的具体办法1、…

会声会影2024专业免费版下载附带激活码序列号

&#x1f31f; 会声会影2024&#xff1a;你的视频编辑新伙伴&#xff01;大家好&#xff0c;今天来给你们安利一个超级棒的视频编辑软件——会声会影2024最新版本&#xff01;作为一位热爱创作的小伙伴&#xff0c;找到一款既强大又易用的视频编辑工具真的太重要了。而会声会影…

什么是嵌入式,单片机又是什么,两者有什么关联又有什么区别?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「嵌入式的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01;从科普的角度&#xff0c;…

【减法网络】Minusformer:通过逐步学习残差来改进时间序列预测

摘要 本文发现泛在时间序列(TS)预测模型容易出现严重的过拟合。为了解决这个问题&#xff0c;我们采用了一种去冗余的方法来逐步恢复TS的真实值。具体来说&#xff0c;我们引入了一种双流和减法机制&#xff0c;这是一种深度Boosting集成学习方法。通过将信息聚合机制从加法转…

Java线程池七个参数详解

ThreadPoolExecutor 是JDK中的线程池实现&#xff0c;这个类实现了一个线程池需要的各个方法&#xff0c;它提供了任务提交、线程管理、监控等方法 下面是 ThreadPoolExecutor 类的构造方法源码&#xff0c;其他创建线程池的方法最终都会导向这个构造方法&#xff0c;共有7个参…

Spring Boot -- 图书管理系统(登录、展示+翻页、添加/修改图书)

文章目录 一、应用分层二、数据库的设计三、登录功能四、展示列表&#xff08;使用虚构的数据&#xff09;五、翻页 展示功能六、添加图书七、修改图书 一、应用分层 为什么我们需要应用分层&#xff1a;当代码量很多时&#xff0c;将其全部放在一起查找起来就会很麻烦&#…

通过MindSpore API实现深度学习模型

快速入门 将相应的包逐一导入到项目中&#xff0c;这是制作项目的第一步。 import mindspore from mindspore import nn from mindspore.dataset import vision, transforms from mindspore.dataset import MnistDataset 处理数据集 先从网上下载对应的数据集文件,MindSpor…

【LeedCode】二分查找算法(一)

二分查找算法的时间复杂度是O(logN) &#xff0c;更优于传统的遍历数组算法值得我们学习。 注意二分查找一般使用的前提是&#xff1a;待操作的数组的元素有某种规律也就是要有二阶性&#xff0c;二阶性就是在数组中选取一点根据该数组元素某种规律可以把数组分为两部分&#x…

Kafka基础教程

Kafka基础教程 资料来源&#xff1a;Apache Kafka - Introduction (tutorialspoint.com) Apache Kafka起源于LinkedIn&#xff0c;后来在2011年成为一个开源Apache项目&#xff0c;然后在2012年成为一流的Apache项目。Kafka是用Scala和Java编写的。Apache Kafka是基于发布-订…

2024广东省职业技能大赛云计算赛项实战——Minio服务搭建

Minio服务搭建 前言 这道题是比赛时考到的&#xff0c;没找到具体题目&#xff0c;但在公布的样题中找到了&#xff0c;虽然很短~ 使用提供的 OpenStack 云平台&#xff0c;申请一台云主机&#xff0c;使用提供的软件包安装部署 MINIO 服务并使用 systemctl 管理 Minio是一个…

SAR动目标检测系列:【4】动目标二维速度估计

在三大类杂波抑制技术(ATI、DPCA和STAP)中&#xff0c;STAP技术利用杂波与动目标在二维空时谱的差异&#xff0c;以信噪比最优为准则&#xff0c;对地杂波抑制的同时有效保留动目标后向散射能量&#xff0c;有效提高运动目标的检测概率和动目标信号输出信杂比&#xff0c;提供理…

2024华为OD机试真题- 计算三叉搜索树的高度-(C++/Python)-C卷D卷-100分

2024华为OD机试题库-(C卷+D卷)-(JAVA、Python、C++) 题目描述 定义构造三叉搜索树规则如下: 每个节点都存有一个数,当插入一个新的数时,从根节点向下寻找,直到找到一个合适的空节点插入。查找的规则是: 1.如果数小于节点的数减去500,则将数插入节点的左子树 2.如果数大于…

微软Azure AI更新视频翻译和语音翻译 API 功能!企业适用TTS文本转语音

很高兴与大家分享 Azure AI 语音翻译产品套件的两个重大更新&#xff01; 分别是视频翻译和增强的实时语音翻译 API。 视频翻译&#xff08;批量&#xff09; 微软宣布推出视频翻译预览版&#xff0c;这是一项突破性的服务&#xff0c;旨在改变企业本地化视频内容的方式。 随着…