【C++学习】C++智能指针:提高代码安全与性能的利器

news2024/11/25 23:51:22

文章标题

  • 智能指针的提出
  • 智能指针概念及使用
    • RAII
  • 智能指针的原理
  • C++库多种智能指针详解
    • 版本一:std::auto_ptr(C++98)
      • 1. std::auto_ptr 使用
      • 2. std::auto_ptr 原理
      • 3. std::auto_ptr 模拟实现
    • 版本二:unique_ptr (C++11)
      • 1. unique_ptr 的使用
      • 2. unique_ptr 的原理
      • 3. unique_ptr 的模拟实现
    • 版本三: std::shared_ptr(C++11)
  • 循环引用问题
      • weak_ptr的简单实现


智能指针的提出

在上篇所将的C++异常的文章中,有一个这样的场景:在new/malloc一个空间到delete/free释放这段资源之间,抛异常了,抛异常后直接跳到了catch语句,导致后面的delete/free语句没有执行到,会导致内存泄漏问题。
如下面这个场景:

  • 如下代码,当main函数调用func函数时,func函数内部给ptr开辟了一个空间,然后本来是要在func函数结束时将ptr空间释放的,但是中间如果抛了异常,接直接跳到了catch语句,导致ptr空间没有被释放,造成内存泄漏。
void func(){
	int* ptr = new int(2);   
	throw("异常");
	delete ptr;
}
int main()
{
	try {
		func();
	}catch (const char* s){
		cout << s << endl;
	}catch (...){
		cout << "未知异常" << endl;
	}
	return 0;
}

如何避免内存泄漏

  1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:
    这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条 智能指针 来管理才有保证。
  2. 采用RAII思想或者智能指针来管理资源。

智能指针概念及使用

RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。(获取到一个资源后,拿去初始化一个对象,并且使用完将其释放)。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。
这种做法有两大好处:

  • 不需要显式地释放资源。
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效。

例如下面的例子:

//使用RAII思想设计的Smart_Pointer类
//将开好的资源给Smart_Pointer这个类创建一个对象,该对象里
//面是一个指针,这个对象来管理这个资源,当对象出了作用域,
//自动调用析构函数将资源清理释放,这样就不会有忘记释放资源
//或则上面中间异常导致delete不到的问题。
template<class T>
class Smart_Pointer{      
public:
	Smart_Pointer(T* ptr)
		:_ptr(ptr)
	{}
	~Smart_Pointer(){
		cout << "~Smart_Pointer" << endl;
		delete _ptr;
	}
private:
	T* _ptr;
};

void func()
{
	Smart_Pointer<int> ptr(new int);   
	throw("异常");
}
int main(){
	try {
		func();
	}catch (const char* s){
		cout << s << endl;
	}catch (...){
		cout << "未知异常" << endl;
	}
	return 0;
}


智能指针的原理

上述的SmartPointer 还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容,因此:SmartPointer模板类中还得需要重载 * 、->,才可让其像指针一样去使用。

举个例子:

template<class T>
class Smart_Pointer
{
public:
	Smart_Pointer(T* ptr)
		:_ptr(ptr)
	{}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	~Smart_Pointer()
	{
		cout << "~Smart_Pointer" << endl;
		delete _ptr;
	}
private:
	T* _ptr;
};
struct Date{
	Date(int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
	~Date(){
		_year = _month = _day = 0;
	}
	int _year;
	int _month;
	int _day;
};
int main(){
	Smart_Pointer<int> ptr1(new int(1));
	cout << *ptr1 << endl;

	Smart_Pointer<Date> ptr2(new Date(2024, 4, 10));
	cout << ptr2->_year <<" " << ptr2->_month << " " << ptr2->_day << " " << endl;

	return 0;
}
//运行结果:
//1
//2024 4 10
//~Smart_Pointer
//~Smart_Pointer

但是,在实际中,我们可能将指针指向另一个空间,或指针间的拷贝,赋值等问题。
那么,智能指针的拷贝是需要深拷贝还是浅拷贝呢?

答案是:浅拷贝,因为智能指针的行为是模拟指针的行为。
之前学习的数据结构:比如list,vector等容器,特点是,利用这些资源存储管理数据,资源是自己的,拷贝时希望资源各自一份,不是同时利用同一份资源。
而智能指针与迭代器类似,资源不是自己的,代管资源,拷贝的时候希望直指向同一块资源;

那么智能指针的拷贝赋值等问题怎么解决呢?那我们就来看看C++中智能指针的历史发展。


C++库多种智能指针详解

  • 智能指针发展历史
  1. C++ 98 中产生了第一个智能指针auto_ptr.
  2. C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr.
  3. C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。
  4. C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。

版本一:std::auto_ptr(C++98)

文档链接:https://legacy.cplusplus.com/reference/memory/auto_ptr/?kw=auto_ptr

1. std::auto_ptr 使用

//包头文件memory
#include <memory>
int main()
{
	auto_ptr<int> ptr1(new int(10));
	auto_ptr<int> ptr2(new int(30));

	ptr1 = ptr2;   
	
	cout<<*ptr1<<endl;   //30
}

2. std::auto_ptr 原理

//当我们打印ptr1的内容时,运行报错
int main()
{
	auto_ptr<int> ptr1(new int(10));
	auto_ptr<int> ptr2(ptr1);

	cout << *ptr2 << endl;   //10
	//cout << *ptr1 << endl;    //会报错
}

在这里插入图片描述

运行调试时发现:

  • ptr2拷贝构造ptr1前:
    在这里插入图片描述

  • ptr2拷贝构造ptr1后
    在这里插入图片描述

  • 原理总结:
    根据调试结果可以分析出:
    std::auto_ptr是将被拷贝对象值拷贝给拷贝对象,然后将被拷贝对象置空处理;

3. std::auto_ptr 模拟实现

C++98版本的库中就提供了auto_ptr的智能指针。
auto_ptr的实现原理:管理权转移的思想,下面简化模拟实现了一份。

template<class T>
class auto_ptr
{
public:
	auto_ptr(T* ptr)
		:_ptr(ptr)
	{}
	auto_ptr(auto_ptr<T>& p)
	{
		_ptr = p._ptr;
		p._ptr = nullptr;
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	~auto_ptr()
	{
		cout << "~Smart_Pointer" << endl;
		delete _ptr;
	}
private:
	T* _ptr;
};

版本二:unique_ptr (C++11)

文档链接https://legacy.cplusplus.com/reference/memory/unique_ptr/?kw=unique_ptr
unique_ptr 版本的智能指针解决拷贝问题,简单粗暴,直接禁止拷贝和赋值。

1. unique_ptr 的使用

//包头文件memory
#include <memory>
int main()
{
	unique_ptr <int> ptr1(new int(10));
	//unique_ptr <int> ptr2(ptr1);   //编译报错
	
	cout << *ptr2 << endl;
	//cout << *ptr1 << endl;   
}

2. unique_ptr 的原理

原理:将unipue_ptr里面的拷贝构造与赋值运算符直接禁掉;

3. unique_ptr 的模拟实现

template<class T>
class unique_ptr
{
public:
	unique_ptr(T* ptr)
		:_ptr(ptr)
	{}
	unique_ptr(unique_ptr<T>& p) = delete;
	unique_ptr<T>& operator=(unique_ptr<T>& p) = delete;
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	~unique_ptr()
	{
		cout << "~unique_ptr()" << endl;
		delete _ptr;
	}
private:
	T* _ptr;
};

版本三: std::shared_ptr(C++11)

文档链接https://legacy.cplusplus.com/reference/memory/shared_ptr/?kw=unique_ptr
shared_ptr 允许拷贝。

  1. shared_ptr使用
int main()
{
	shared_ptr<int> ptr1(new int(10));
	shared_ptr<int> ptr2(ptr1);   //可拷贝构造
	shared_ptr<int> ptr3;

	ptr3 = ptr1;    //可赋值

	cout << *ptr2 << endl;
	cout << *ptr1 << endl;
	cout << *ptr3 << endl;
}
  1. shared_ptr原理

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。

  • shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
  • 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
  • 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
  • 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

注意:share_ptr不允许隐式类型转换

思考:为什么不能用静态成员变量来计数?
如图所示:
在这里插入图片描述
解析
当使用静态成员变量来计数,如图所示,开始时ptr1指向的一个空间,当用ptr1拷贝构造或则复制拷贝一个对象(ptr2)时,只需要_count++,当他们中的一个对象析构时,只需要_count–,当_count等于1时,说明只有一个对象管控着这个资源,当这个对象析构的时候,释放这块空间;

用静态成员变量虽然可以解决这种场景,但是当重新构造另一个对象的时候,该对象引用计数应该为1,但是这里却不为一。
所以用静态成员变量计数行不通。

解决方法:
对象里面存储一个int*的指针,构造的时候将这个空间的值初始化为1,当拷贝构造或者析构的时候,只需要++,–即可;
如图:
在这里插入图片描述

  1. shared_ptr模拟实现
template<class T>
class shared_ptr
{
public:
	shared_ptr(T* ptr=nullptr)
		:_ptr(ptr)
		,_count(new int(1))
	{}
	//拷贝构造
	shared_ptr(shared_ptr<T>& p){
		assert(_ptr != p._ptr);
		_ptr = p._ptr;
		_count = p._count;
		++(*_count);
	}
	//ptr1=ptr2
	shared_ptr<T>& operator=(shared_ptr<T>& p)
	{
		//判断自己赋值给自己,或则间接自己给自己赋值
		if (_ptr != p._ptr){
			if (--(*_count) == 0){
				delete _ptr;
				delete _count;
			}
			_ptr = p._ptr;
			_count = p._count;
			++(*_count);
		}
		return *this;
	}
	int getconut()
	{
		return *(_count);
	}
	T& operator*(){
		return *_ptr;
	}
	T* operator->(){
		return _ptr;
	}
	~shared_ptr(){
		cout << "~shared_ptr()" << endl;
		//当引用计数--后为0,则释放
		if (--(*_count)==0)
		{
			delete _ptr;
			delete _count;
		}
	}
private:
	T* _ptr;
	int* _count;
};

循环引用问题

  1. node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动delete。
  2. node1的_next指向node2,node2的_prev指向node1,引用计数变成2。
  3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点。
  4. 也就是说_next析构了,node2就释放了。
  5. 也就是说_prev析构了,node1就释放了。
  6. 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放。

在这里插入图片描述
解决方案:
在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了
原理就是,node1->_next = node2;和node2->_prev = node1;
时weak_ptr的_next和_prev不会增加node1和node2的引用计数。

weak_ptr的简单实现

代码实现:

template<class T>
class weak_ptr
{
public:
	weak_ptr()
		:_ptr(nullptr)
	{}
	weak_ptr(shared_ptr<T>& p)
	{
		_ptr = p.get();
	}
	shared_ptr<T>& operator=(shared_ptr<T>&  p)
	{
		_ptr = p.get();
		return *this;
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	~weak_ptr()
	{
		cout << "~weak_ptr()" << endl;
		delete _ptr;	
	}
	private:
		T* _ptr;
	};

本章完~

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

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

相关文章

春招百题--堆--扩展篇--找出最小

其他类似题目&#xff1a; 373. 查找和最小的 K 对数字378. 有序矩阵中第 K 小的元素719. 找出第 K 小的数对距离786. 第 K 个最小的素数分数 2040. 两个有序数组的第 K 小乘积 2386. 找出数组的第 K 大和 215. 数组中的第K个最大元素 不纠结直接sort排序解决。 class Solut…

SpringBoot项目接入Nacos注册中心

前置 已经安装好Nacos服务&#xff0c;并且该项目所在服务器可以访问到 可以参考下&#xff1a; windows环境安装Nacos单机版-CSDN博客 Centos7安装Nacos单机版-CSDN博客 1. POM文件引入依赖 注意&#xff0c;父工程已经引入spring cloud依赖管理的情况下不用添加版本号 …

基于opencv的视觉巡线实现

前言 这段时间在和学弟打软件杯的比赛&#xff0c;有项任务就是机器人的视觉巡线&#xff0c;这虽然不是什么稀奇的事情&#xff0c;但是对于一开始不了解视觉的我来说可以说是很懵了&#xff0c;所以现在就想着和大家分享一下&#xff0c;来看看是如何基于opencv来实现巡线的…

天猫商品详情数据接口(Tmall.item_get)天猫搜索商品列表接口

天猫商品详情数据接口&#xff08;Tmall.item_get&#xff09;是天猫开放平台提供的一种API接口&#xff0c;旨在帮助开发者获取天猫平台上的商品详情信息。通过调用这个接口&#xff0c;开发者可以获取包括商品ID、标题、价格、库存量、图片等在内的详细数据&#xff0c;从而更…

由elemnent-ui模拟一个全选、反选效果想到的购物车逻辑案例

本文参考 https://blog.csdn.net/sumimg/article/details/137508302?csdn_share_tail%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22137508302%22%2C%22source%22%3A%22sumimg%22%7D 我遇到的问题 点击店铺二级的时候&#xff0c;checkedCiti…

HNUST湖南科技大学嵌入式开发板使用-2024

目录 1.需要准备的软件(版本必须相同)꒰ঌ( ⌯ ⌯)໒꒱ 2.下载链接地址⌯▾⌯ 3.软件安装教程 4.安装好了&#xff0c;正常情况会是什么样子呢&#xff1f;(๑•̌.•๑) 4.1.拆入第一个接口(串口com接口是用来上传代码的ฅ˙Ⱉ˙ฅ) 4.2.拆入第三个接口&#xff08;SWD Jlink口…

鸿蒙应用开发之富文本(RichText)组件

前面学习了评分组件,现在来学习富文本组件,这个组件用来表示复杂的文本,把文本显示得更加有特色,比如网页一样显示。这种显示会比较复杂,所以应用的场合就会少一点。不过富文本显示最多的,就是即时通讯软件了,比如显示图片与文本,以及一些特殊的字符。 比如显示如下面的…

Unity Standalone File Browser,Unity打开文件选择器

Unity Standalone File Browser&#xff0c;Unity打开文件选择器 下载地址&#xff1a;GitHub链接&#xff1a; https://github.com/gkngkc/UnityStandaloneFileBrowser简单的示例代码 using SFB; using System; using System.IO; using UnityEngine; using UnityEngine.UI;…

【自然语言处理八-transformer实现翻译任务-一(输入)】

自然语言处理八-transformer实现翻译任务-一&#xff08;输入&#xff09; transformer架构数据处理部分模型的输入数据(图中inputs outputs outputs_probilities对应的label)以处理英中翻译数据集为例的代码 positional encoding 位置嵌入代码 鉴于transfomer的重要性&#xf…

Ja-netfilter(idea激活码破解原理)分析

Ja-netfilter&#xff08;idea破解&#xff09;分析 简介 ja-netfilter是一款可以破解jetbrainsIDE系列的javaagent jar 包。 原理简介 通过javaagent创造修改字节码的时机。通过asm修改相关类&#xff0c;达到hook特定方法的作用。主要依赖power&#xff08;rsa方法hook&a…

【Java】Set集合的基本使用

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 一、HashSet集合 1.HashSet集合的特点 2.HashSet常用方法 ①&#xff1a;add(Object o)&#xff1a;向Set集合中添加元素&#xff0c;不允许添加重复数据。 ②&#xff1a;size()&#xff1a;返回Set集合中的元素个数…

NASA数据集——亚洲夏季季风化学与气候影响项目超高灵敏度气溶胶光谱(UHSAS)数据

ACCLIP_Aerosol_AircraftInSitu_WB57_Data 简介 ACCLIP_Aerosol_AircraftInSitu_WB57_Data 是亚洲夏季季风化学与气候影响项目&#xff08;ACCLIP&#xff09;期间收集的原地气溶胶数据。本数据集收录了来自下一代激光质谱仪&#xff08;PALMS-NG&#xff09;、单颗粒烟尘光度…

0101tomcat部署war访问mysql失败-容器间通信-docker项目部署

文章目录 一、简介二、部署1、mysql数据迁移2、docker部署redis3、docker部署tomcat并运行war包 三、报错四、解决1 分析2 解决 结语 一、简介 最近参与开发一个项目&#xff0c;其中一部分系统需要迁移。从阿里云迁移到实体服务器&#xff0c;使用docker部署。系统使用Java语…

RAG进阶之通用文档处理:从RAGFlow、TextMonkey到mPLUG-DocOwl 1.5

前言 我司RAG项目组每个月都会接到一些B端客户的项目需求&#xff0c;做的多了&#xff0c;会发现很多需求是大同小异的&#xff0c;所以我们准备做一个通用的产品&#xff0c;特别是对通用文档的处理 而在此之前&#xff0c;我们则想先学习一下目前市面上各种优秀的解决方法…

IIS服务器更换即将过期的SSL证书

公司IIS服务器证书快要过期&#xff0c;替换证书的步骤&#xff1a; Winr输入mstsc命令&#xff0c;显示远程登录&#xff1b;输入服务器IP以及密码&#xff0c;进行远程登陆登陆IIS服务器&#xff0c;winr输入inetmgr命令显示IIS操控器&#xff1b;选择服务器证书--点击服务器…

成为摄影拍照高手,摄影技术进阶秘籍

一、资料前言 本套摄影高手资料&#xff0c;大小2.02G&#xff0c;共有57个文件。 二、资料目录 DSLR数码单反摄影圣经.pdf photoshop超细的人像后期磨皮及专业美化.docx “失传”的人像拍摄绝技.doc 白加黑减.怎样应用曝光补偿.pdf 标准镜头秘笈&#xff1a;标准镜如何…

快速寻找可以构建出网通信隧道的计算机

点击星标&#xff0c;即时接收最新推文 本文选自《内网安全攻防&#xff1a;红队之路》 扫描二维码五折购书 为加强内网的安全防范&#xff0c;安全管理员往往会限制内网计算机访问互联网&#xff0c;当然不同机构的限制策略是不一样的&#xff0c;有的完全阻断了内网计算机访问…

langchain LCEL,prompt模块,outputparse输出模块

目录 基本代码 prompt模块 prompt模版控制长度 outputparse格式化输出 并行使用调用链 LangChain表达式语言&#xff0c;或者LCEL&#xff0c;是一种声明式的方式&#xff0c;可以轻松地将链条组合在一起 langchian 可以使用 通义千问&#xff0c;我们用通义千问&#x…

openkylin系统通过网线连接ubuntukylin系统上网攻略

openkylin系统通过网线连接ubuntukylin系统上网攻略 主机1&#xff1a;x64 amd &#xff0c;系统&#xff1a;ubuntukylin 22.04 &#xff0c;状态&#xff1a;通过wifi连接热点进行上网&#xff0c;并共享网络。 主机2&#xff1a;x64 intel &#xff0c;系统&#xff1a;ope…

阿里一面:如何将重复性比较高的 String 类型的地址信息从 20GB 降到几百兆?...

这次应该是互联网及软件行业的第三次寒潮&#xff0c;大家在寒潮中一定要继续保持学习&#xff0c;寒潮挺过去以后还是会迎来新的发展机遇。 有粉丝去阿里面试&#xff0c;跟码哥分享了其中一题面试问题「如何将重复性比较高的 String 类型的地址信息从 20GB 降到几百兆&#x…