C++---智能指针

news2024/9/25 11:21:31

目录

1. 为什么需要智能指针?

2. 内存泄漏

2.1 什么是内存泄漏,内存泄漏的危害

2.2 内存泄漏分类

2.4如何避免内存泄漏

3.智能指针的使用及原理

3.1 RAII

3.2 智能指针的原理

3.3 std::auto_ptr

3.4 std::unique_ptr

3.5 std::shared_ptr

3.6 std::shared_ptr的循环引用

1. 为什么需要智能指针?

        下面我们先分析一下下面这段程序有没有什么内存方面 的问题?提示一下:注意分析 MergeSort 函数中的问题。
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;//p1抛异常,程序从这一步直接跳catch,没问题
	int* p2 = new int;//p2抛异常,程序从这一步跳catch,p1无法释放,资源泄露
	cout << div() << endl;//这里抛异常,调到catch,p1,p2无法释放,资源泄露
	delete p1;
	delete p2;
}
int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

2. 内存泄漏

2.1 什么是内存泄漏,内存泄漏的危害

什么是内存泄漏 :内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。
存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对
该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害 :长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现
内存泄漏会导致响应越来越慢,最终卡死。
void MemoryLeaks()
{
   // 1.内存申请了忘记释放
  int* p1 = (int*)malloc(sizeof(int));
  int* p2 = new int;
  
  // 2.异常安全问题
  int* p3 = new int[10];
  
  Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
  
  delete[] p3;
}

2.2 内存泄漏分类

C/C++ 程序中一般我们关心两种方面的内存泄漏:
堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据须要分配通过 malloc / calloc / realloc / new 等从堆中分配的一
块内存,用完后必须通过调用相应的 free 或者 delete 删掉。假设程序的设计错误导致这部分
内存没有被释放,那么以后这部分空间将无法再被使用,就会产生 Heap Leak
系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放
掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

2.4如何避免内存泄漏

1. 工程前期良好的设计规范, 养成良好的编码规范,申请的内存空间记着匹配的去释放 ps
这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。
2. 采用 RAII思想 或者 智能指针 来管理资源。
3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
4. 出问题了使用内存泄漏工具检测。 ps :不过很多工具都不够靠谱,或者收费昂贵。
总结:
内存泄漏非常常见,解决方案分为两种:
        1、事前预防型。如智能指针等。
        2 、事后查错型。如泄漏检测工具。

3.智能指针的使用及原理

3.1 RAII

        RAII( Resource Acquisition Is Initialization )是一种 利用对象生命周期来控制程序资源 (如内 存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源 ,接着控制对资源的访问使之在对象的生命周期内始终保持有效, 最后在
对象析构的时候释放资源 。借此,我们实际上把管理一份资源的责任托管给了一个对象。
这种做法有两大好处:
1. 不需要显式地释放资源。
2. 采用这种方式,对象所需的资源在其生命期内始终保持有效。
// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	~SmartPtr()
	{
		if (_ptr)
			delete _ptr;
	}

private:
	T* _ptr;
};
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
	SmartPtr<int> sp1(new int);//类似之前p1指针
	SmartPtr<int> sp2(new int);//类似之前p2指针
	cout << div() << endl;//即使这里报错,上面new的资源也会在栈帧销毁时释放
}
int main()
{
	try {
		Func();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

3.2 智能指针的原理

上述的 SmartPtr 还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可
以通过 -> 去访问所指空间中的内容,因此: AutoPtr 模板类中还得需要将operator * 、operator -> 重载下,才可让其 像指针一样去使用
template<class T>
class SmartPtr {
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	~SmartPtr()
	{
		if (_ptr)
			delete _ptr;
	}
	T& operator*() { return *_ptr; }
	T* operator->() { return _ptr; }
private:
	T* _ptr;
};
struct Date
{
	int _year;
	int _month;
	int _day;
};
int main()
{
	SmartPtr<int> sp1(new int);
	*sp1 = 10;
	cout << *sp1 << endl;
	SmartPtr<Date> sparray(new Date);
	// 需要注意的是这里应该是sparray.operator->()->_year = 2018;
	// 本来应该是sparray->->_year这里语法上为了可读性,省略了一个->
	sparray->_year = 2018;
	sparray->_month = 1;
	sparray->_day = 1;
}
总结一下智能指针的原理:
1. RAII特性
2. 重载operator*和opertaor->,具有像指针一样的行为。

3.3 std::auto_ptr

C++98 版本的库中就提供了 auto_ptr 的智能指针。下面演示的 auto_ptr 的使用及问题。
auto_ptr的实现原理 管理权转移的思想 ,下面简化模拟实现了一份 zj::auto_ptr 来了解它的原
namespace zj
{
	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 = NULL;
			}
			return *this;
		}
		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}
		// 像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};
}
//结论:auto_ptr是一个失败设计,很多公司明确要求不能使用auto_ptr
int main()
{
 std::auto_ptr<int> sp1(new int);
 std::auto_ptr<int> sp2(sp1); // 管理权转移

 // sp1悬空
 *sp2 = 10;
 cout << *sp2 << endl;
 cout << *sp1 << endl;
 return 0;
}

3.4 std::unique_ptr

C++11 中开始提供更靠谱的 unique_ptr
unique_ptr的实现原理 简单粗暴的防拷贝 ,下面简化模拟实现了一份 UniquePtr 来了解它的原
namespace zj
{
    template<class T>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}

        //防拷贝
		unique_ptr(unique_ptr<T>& sp) = delete;
        unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;

		~unique_ptr()
		{
			if (_ptr)
			{
				delete _ptr;
			}
		}

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

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

3.5 std::shared_ptr

C++11 中开始提供更靠谱的并且支持拷贝的 shared_ptr
shared_ptr的原理 :是 通过引用计数的方式来实现多个shared_ptr对象之间共享资源
1. shared_ptr 在其内部, 给每个资源都维护了着一份计数,用来记录该份资源被几个对象共
2. 对象被销毁时 ( 也就是析构函数调用 ) ,就说明自己不使用该资源了,对象的引用计数减1。
3. 如果引用计数是0 ,就说明自己是最后一个使用该资源的对象, 必须释放该资源
4. 如果不是0 ,就 说明除了自己还有其他对象在使用该份资源 不能释放该资源 ,否则其他对象就成野指针了。
namespace zj
{
    template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr)
			:_ptr(ptr)
			,_pCount(new int(1))
		{}

		shared_ptr(shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			,_pCount(sp._pCount)
		{
			++(*_pCount);
		}

		~shared_ptr()
		{
			if (_ptr)
			{
				delete _ptr;
			}
		}

		shared_ptr<T>& operator=(shared_ptr<T>& ap)
		{
			if (_ptr == ap._ptr)
				return *this;

			if (--(*_pCount) == 0)
			{
				delete _ptr;
				delete _pCount;
			}
			_ptr = ap._ptr;
			_ptr = ap._pCount;
			(*_pCount)++;

			return *this;
		}

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

		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pCount;
	};
}

3.6 std::shared_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;
}

循环引用分析:
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就可以了 。

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

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

相关文章

使用RMAN传输数据_创建可传输的表空间集(Creating Transportable Tablespace Sets)

传输数据比对相同的数据执行export/import或unload/load操作更快。因为对于用户定义的表空间&#xff0c;数据文件包含所有拷贝到目标位置的实际数据&#xff0c;你使用Data Pump只传输数据库对象的元数据到新数据库。 本章阐述如何使用RMAN通过还原备份来创建可传输的表空间集…

Dockfile是什么

目录 1. Dockfile是什么 2. Dockerfile的基本组成 2.1 FROM 2.2 MAINTAINER 2.3 RUN 2.4 COPY 2.5 ADD 2.6 EXPOSE 2.7 WORKDIR 2.8 ONBUILD 2.9 USER 2.10 VOLUME 2.11 CMD 2.12 ENTRYPOINT 3. dockerfile示例 3.1 准备 3.2 将该目录上传至linux 3.3 构建镜像…

2022年「博客之星」参赛博主:一个处女座的测试

我正在参加年度博客之星评选&#xff0c;请大家帮我投票打分&#xff0c;您的每一分都是对我的支持与鼓励。 五星必回&#xff0c;诚信互评&#xff0c;&#xff08;如果&#xff09;今日已满&#xff0c;明天必回&#xff0c;言出必行&#xff0c;感谢支持&#xff01; 我正在…

【深度探讨】数据存储进化论,区块链才是未来

发表时间&#xff1a;2022年5月23日 信息来源&#xff1a;bsvblockchain.org 需要一个适于处理这种日渐普及的资源的基础设施。 2022年2月&#xff0c;在沙特愿景2030区块链峰会上&#xff0c;BSV区块链协会创始主席Jimmy Nguyen就数据的价值这一话题发表了一场令人叹为观止的…

nodejs+vue+element+eachers构建开源项目大型连续剧(2)安装mysql数据库,在nodejs服务器中操作数据库数据

太长时间没更新了&#xff0c;然后&#xff0c;理由是什么呢&#xff1f;是因为阳了&#xff0c;真没想到&#xff0c;吃嘛嘛香的我忽然阳了&#xff0c;果然阳的初期症状就是嘴硬。然后&#xff0c;开始我们连续剧的第二集。 一、进行mysql的安装 学习第一步&#xff0c;从安…

[极客大挑战 2019]BuyFlag1(BUUCTF)

前言: 这篇文章还是是为了帮助一些 像我这样的菜鸟 找到简单的题解 题目描述 解题工具: 我爱用edit this cookie2和hackerbar&#xff0c; 当然也可以burpsuite和fiddler抓包 解题过程: 看到他说flag要100000000 MONEY&#xff0c; 还要是Cuits students&#xff0c; …

GCC嵌入汇编特性

前言 在C语言编程中&#xff0c;使用汇编指令有两种方式&#xff0c;一种是使用单独的汇编源文件&#xff0c;在最后编译的时候和其它C工程文件编译到一起&#xff0c;这种方式能够做到汇编函数和C函数的互相调用&#xff0c;但是如果希望在C语言中直接调用汇编表达式就不是那…

kibana在linux环境安装实战遇到的问题汇总

kibana在linux环境安装实战遇到的问题汇总&#xff1a; 1、找不到主节点的错误&#xff1a; savedobjects-service] Unable to connect to Elasticsearch. Error: [master_not_discovered_exception] null 原因是找不到es的主节点&#xff0c;意思是es没有配置主节点。 解决…

【Spring】Spring @Cacheable 官方学习及demo

文章目录前言Cacheable 的来源应用场景集成Redis的思路代码及验证后记前言 Spring 有很多声明式的编程风格&#xff0c;Transactional 是&#xff0c;Cacheable 也是。说起 Transactional&#xff0c;复杂的事务情况下&#xff0c;这个注解也有局限&#xff0c;需要用到编程式…

奇安信病毒检测中心 2022年第二季度App收集个人信息检测报告 学习笔记 附下载地址

奇安信 2022年第二季度App收集个人信息检测报告 下载地址 2022年第二季度 APP收集个人信息 检测报告 奇安信 病毒响应中心 研究背景 随着互联网和移动设备的发展&#xff0c;手机已成为人人都拥有的设备&#xff0c;其中各式各样的APP更是丰富了人们的生活&#xff0c;从社交…

现在考系统集成项目管理工程师有用吗?

有用&#xff01; 现在考系统集成项目管理工程师还是挺有用的哈&#xff0c;虽然它资质评定国务院取消了&#xff0c;但它是由国家人力资源和社会保障部、工业和信息化部领导下的国家级考试&#xff0c;并且是职业资格考试、职称资格考试和水平考试。 系统集成项目管理工程师…

sql-labs靶场环境搭建及搭建过程遇到的问题

sql-labs是github上的一个开源项目&#xff0c;通过闯关的方式训练自己的SQL注入。 下载php-study 进入官网 php-study 下载sql-labs 进入github sql-labs 将sql-labs下载到php-study的WWW\目录下&#xff0c;解压。 配置sql-llibs 记事本打开sqli-labs-master\sql-conn…

用python采集四大电商平台商品数据进行对比

前言 大家早好、午好、晚好吖 ❤ ~ 环境介绍: python 3.8 越稳定越好 pycharm 2021专业版 一、考拉 模块使用: requests >>> pip install requests parsel >>> pip install parsel 代码展示 导入模块 # import 导入模块 import requests # 第三方…

这届黑客不讲武德

编者按 腾讯安全2022年典型攻击事件复盘第七期&#xff0c;希望帮助企业深入了解攻击手法和应对措施&#xff0c;完善自身安全防御体系。 本篇讲述了某物流公司遭遇不明黑客攻击&#xff0c;腾讯安全服务团队和客户通力合作&#xff0c;排查溯源&#xff0c;最后揪出黑客的尾…

十、HashMap详解

文章目录小结底层源码框架特点源码详解小结 Map接口的常用实现类:HashMap、Hashtable和Properties.HashMap是Map接口使用频率最高的实现类。HashMap是以 key-val对的方式来存储数据(HashMap$Node类型)key不能重复&#xff0c;但是值可以重复,允许使用null键和null值。如果添加…

Android设计模式详解之组合模式

前言 组合模式也称为部分整体模式&#xff0c;结构型设计模式之一&#xff1b; 定义&#xff1a;将对象组合成树形结构以表示“部分-整体”的层次结构&#xff0c;使得用户对单个对象和组合对象的使用具有一致性&#xff1b; 使用场景&#xff1a; 表示对象的部分-整体的层…

屏幕录制为什么没声音?检查这2项,轻松解决

相信很多人在录制视频的时候都会遇到各种各样的问题&#xff0c;比如录制的视频没有声音。屏幕录制为什么没声音&#xff1f;今天小编就和大家分享一下如何录制音画同步视频的具体操作方法。如果你有录制的视频没有声音&#xff0c;你可以试试这个方法。 一、检查是否打开电脑系…

amCharts 5: Maps 附加组件

amCharts 5: Maps 附加组件 将reverseGeodata设置添加到MapPolygonSeries。如果设置为true&#xff0c;将反转GeoJSON数据中多边形角的方向。 向MapLine添加了新的linechanged事件。 amCharts 5&#xff1a;地图 amCharts:Maps是amCharts:Charts的附加组件。使用它可以将完整的…

Java学习笔记【7】面向对象

⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ &#x1f300; 个人首页&#xff1a;&#x1f3e0; 星空之路Star &#x1f3e0; &#x1f300; 所属专栏&#xff1a;&#x1f4d6; Java知识点总结 &#x1f4d6; &#x1f300; 大家好&#x1f91d; 我是 &#x1f449;老孙&#x1f448; &…

操作系统期末考试必会题库5——文件管理

1、画出文件系统的层次模型&#xff0c;并描述各层的功能。 2、某文件系统为一级目录&#xff0c;文件的数据一次性写入磁盘&#xff0c;已经写入的文件不可修改&#xff0c;但可以多次创建新文件&#xff0c;请回答&#xff1a; (1) 采用哪种文件物理结构形式更适合&#xff…