深入解析智能指针:从实践到原理

👦个人主页晚风相伴

👀如果觉得内容对你有所帮助的话,还请一键三连(点赞关注收藏

如果内容有错或者不足的话,还望你能指出。

目录

智能指针的引入

内存泄漏

RAII

智能指针的使用及原理

std::auto_ptr

std::unique_ptr

std::shared_ptr

std::weak_ptr

定制删除器


智能指针的引入

先看一下下面的代码

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
	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;
}

分析上面的代码,我们会发现代码运行时可能会出现以下问题

  1. 如果p1那里new失败而抛异常
  2. 如果P2那里new失败而抛异常
  3. 如果div调用那里发生除0错误而抛异常

在上面代码中如果出现以上抛异常的情况则会导致资源得不到释放从而导致内存泄漏的问题,那么有什么好的办法可以解决吗?

别说还真有一个好办法,那就是使用智能指针。

在C++中,我们经常会使用new和delete来动态分配和释放内存。但是这种手动管理的方式如果我们忘记在最后调用delete或者在delete抛异常的话就很容易出现一些内存泄漏等问题,另外,如果我们多次释放同一块内存或者在释放内存后继续使用这块内存,就会引起运行时错误。所以为了解决这里的这些问题,C++就引入了智能指针的概念。

智能指针的概念

智能指针是一种特殊的数据类型,其目的是管理动态分配的资源,尤其是堆内存。智能指针是用类模板的方式实现的并且使用RAII(资源获取即初始化)技术来确保资源的正确释放,从而避免内存泄漏和野指针的问题。

内存泄漏

 在介绍智能指针之前先来了解一下什么是内存泄漏以及内存泄漏的危害。

内存泄漏是指因为疏忽或者错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该内存的控制,从而造成了内存的浪费。

内存泄漏的危害:如果长期运行的程序出现内存泄漏,影响会很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

可以下面这段代码直观体会一下内存泄漏的危害

int main()
{
	char* p = new char[1024 * 1024 * 1024];
	cout << (void*)p << endl;
	return 0;
}

在代码没有运行起来时,我的内存是这么多。

在代码运行起来后,一瞬间我的内存就少了1个G。

可见如果代码中有内存泄漏的问题将会发生很严重的后果。

👍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)
		{
			cout << "Delete:" << _ptr << endl;//方便观察
			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);
    SmartPtr<int> sp2(new int);
	cout << div() << endl;

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

这样上面的抛异常所带来的内存泄漏问题就能迎刃而解了

 

智能指针的使用及原理

在上面利用RAII思想设计的SmartPtr还不能称作上智能指针,因为上面的SmartPtr只是实现了构造和析构函数,还没有做到像一个指针一样支持解引用和->访问。

因此基于这样的需求我们就需要使用operator重载来实现相关功能。

template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr = nullptr)
		:_ptr(ptr)
	{}
	~SmartPtr()
	{
		if (_ptr)
		{
			cout << "Delete:" << _ptr << endl;
			delete _ptr;
		}
	}
    //解引用
	T& operator*()
	{
		return *_ptr;
	}
    
    //->引用
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
};

int main()
{
	SmartPtr<pair<string, int>> sp1(new pair<string, int>("print", 1));
	cout << sp1->first << ":" << sp1->second << endl;
	return 0;
}

👊std::auto_ptr

auto_ptr文档介绍

auto_ptr是C++98版本提供的智能指针,但是auto_ptr设计的并不好,导致很多人对它的意见很大以及很多公司都明确规定不去使用它。

auto_ptr的原理其实是使用了一个管理权转移的思想。先来看看库里面auto_ptr是怎么回事,然后我们就自己动手来简单实现一下auto_ptr。

class A
{
public:
	A()
	{}
	~A()
	{
		cout << "~A()" << endl;
	}
	int _a1 = 0;
	int _a2 = 0;
};

void test_auto_ptr()
{
	std::auto_ptr<A> ap1(new A);
	ap1->_a1++;
	ap1->_a2++;

	std::auto_ptr<A> ap2(ap1);
	ap1->_a1++;
	ap1->_a2++;

	ap2->_a1++;
	ap2->_a2++;

	cout << ap2->_a1 << endl;
	cout << ap2->_a2 << endl;

	std::auto_ptr<A> ap3(new A);
	ap2 = ap3;

	ap2->_a1++;
	ap2->_a2++;
	cout << ap2->_a1 << endl;
	cout << ap2->_a2 << endl;
}

可以看出auto_ptr在进行拷贝时其实是用了一个管理权转移的思想,将ap1对A的管理权转移到了ap2,之后再将ap1置空,所以上面程序再往下运行就会崩溃。它的赋值操作也是采用同样的思想。

 

知道了怎么回事之后下面我们自己就动手实现一下吧

namespace hjx
{
    //auto_ptr拷贝和赋值时本质就是管理权转移
	//被拷贝的对象出现悬空问题
	template<class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{}

		auto_ptr(auto_ptr<T>& ap)
			:_ptr(ap._ptr)
		{
			ap._ptr = nullptr;
		}

		auto_ptr<T>& operator=(auto_ptr<T>& ap)
		{
			if (this != &ap)//不能是自己
			{
				if (_ptr)//判断一下是否为空
				{
					cout << "Delete:" << _ptr << endl;//方便观察结果
					delete _ptr;
				}
				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}

			return *this;
		}

		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "Delete:" << _ptr << endl;//方便观察结果
				delete _ptr;
			}
		}
		T& operator*()
		{
			return *_ptr;
		}

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

🔥std::unique_ptr

 unique_ptr文档介绍

 unique_ptr对象拥有对动态分配的内存资源的唯一所有权,不能进行拷贝构造和赋值操作。

 下面就简单模拟实现一下unique_ptr来了解它的原理吧

namespace hjx
{
    //unique_ptr不支持拷贝和赋值
	template<class T>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{}

        //使用C++11中的delete将拷贝构造和赋值禁掉
		unique_ptr(unique_ptr<T>& up) = delete;
		unique_ptr<T>& operator=(unique_ptr<T>& up) = delete;	

		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "Delete:" << _ptr << endl;
				delete _ptr;
			}
		}
		T& operator*()
		{
			return *_ptr;
		}

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

🔥std::shared_ptr

 shared_ptr文档介绍

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

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

下面我们就简单模拟实现一下shared_ptr吧

namespace hjx
{
    //shared_ptr采用引用计数的方式
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_pCount(new int(1))
		{}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			,_pCount(sp._pCount)
		{
			(*_pCount)++;//共同管理新资源,++计数
		}

		void Release()
		{
			if (--(*_pCount) == 0)
			{
				cout << "Delete:" << _ptr << endl;//方便观察现象
				delete _ptr;
				delete _pCount;
			}
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//考虑自己给自己赋值的情况
			if (_ptr != sp._ptr)
			{
				//减减被赋值对象的计数,如果是最后一个对象,要释放资源
				Release();

				//共同管理新资源,++计数
				_ptr = sp._ptr;
				_pCount = sp._pCount;
				(*_pCount)++;
			}
			return *this;
		}

		~shared_ptr()
		{
			Release();
		}
		T& operator*()
		{
			return *_ptr;
		}

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

		int use_count()
		{
			return *_pCount;
		}

		T* get()const
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pCount;//用一个指针来记录计数值
	};
}

但是shared_ptr也并不是完美的,它如果碰到循环引用的情况就会出现问题了

class Node
{
public:
	int _val;
	std::shared_ptr<Node> _next;
	std::shared_ptr<Node> _prev;

	~Node()
	{
		cout << "~Node()" << endl;
	}
};

void test_shared_ptr()
{
	//循环引用问题
	std::shared_ptr<Node> n1(new Node);
	std::shared_ptr<Node> n2(new Node);
	n1->_next = n2;
	n2->_prev = n1;
	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;
}

从打印结果来看,它并没有完成析构的一个操作

 下面几幅图就很好的诠释了循环引用的问题

❔该如何解决这里的问题呢?那就需要用到下面即将要介绍的weak_ptr了 

🔥std::weak_ptr

 weak_ptr文档介绍

weak_ptr不是常规的智能指针,没有RAII,不支持直接管理资源,它被设计出来主要是和shared_ptr搭配使用,解决循环引用的问题。

使用weak_ptr时不会增加引用计数,所以我们就可以将上面问题中的_prev和_next换成weak_ptr就能解决问题了。

class Node
{
public:
	int _val;
	std::weak_ptr<Node> _next;
	std::weak_ptr<Node> _prev;

	~Node()
	{
		cout << "~Node()" << endl;
	}
};

void test_shared_ptr()
{
	std::shared_ptr<Node> n1(new Node);
	std::shared_ptr<Node> n2(new Node);
	n1->_next = n2;
	n2->_prev = n1;
    cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;
}

下面就来简单模拟实现一下weak_ptr吧 

namespace hjx
{
    //weak_ptr是个辅助型智能指针,用来解决shared_ptr循环引用问题
	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}

        //拷贝和赋值不增加引用计数
		weak_ptr(const weak_ptr<T>& wp)
			:_ptr(wp._ptr)
		{}

		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())
		{}

		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			_ptr = sp.get();
			return *this;
		}

		weak_ptr<T>& operator=(const weak_ptr<T>& wp)
		{
			_ptr = wp._ptr;
			return *this;
		}

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

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

👀定制删除器

当我们动态开辟一块数组空间时(如new int[10]),我们必须得使用delete[]来释放空间,否则程序将会崩溃。

例如下面的例子

class A
{
public:
	A()
	{}
	~A()
	{
		cout << "~A()" << endl;
	}
	int _a1 = 0;
	int _a2 = 0;
};

int main()
{
	std::shared_ptr<A> sp2(new A[10]);//程序崩溃
	return 0;
}

因为shared_ptr默认使用的是delete来释放空间

其实当我们使用new来动态开辟一块数组空间时,会多开辟4个字节的空间用来存放这块空间的大小,而我们的sp2正好是指向的这4个字节的空间,当使用delete来释放空间时sp2不会跳过这4个字节的空间,所以导致类型不匹配,进而导致程序就崩溃了。

 

所以我们的shared_ptr还要设计一个仿函数删除器来解决这里的问题。

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

	//shared_ptr采用引用计数的方式
	template<class T, class D = Delete<T>>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_pCount(new int(1))
		{}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			,_pCount(sp._pCount)
		{
			(*_pCount)++;//共同管理新资源,++计数
		}

		void Release()
		{
			if (--(*_pCount) == 0)
			{
				/*cout << "Delete:" << _ptr << endl;//方便观察现象
				delete _ptr;*/
				D()(_ptr);
				delete _pCount;
			}
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//考虑自己给自己赋值的情况
			if (_ptr != sp._ptr)
			{
				//减减被赋值对象的计数,如果是最后一个对象,要释放资源
				Release();

				//共同管理新资源,++计数
				_ptr = sp._ptr;
				_pCount = sp._pCount;
				(*_pCount)++;
			}
			return *this;
		}

		~shared_ptr()
		{
			Release();
		}
		T& operator*()
		{
			return *_ptr;
		}

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

		int use_count()
		{
			return *_pCount;
		}

		T* get()const
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pCount;//用一个指针来记录计数值
	};

    //仿函数删除器
    template<class T>
    struct DeleteArray
    {
	    void operator()(T* ptr)
	    {
		    //cout << "delete[]" << ptr << endl;//可不打印
		    delete[] ptr;
	    }
    };
    
    template<class T>
    struct Free
    {
	    void operator()(T* ptr)
	    {
		    //cout << "free[]" << ptr << endl;//可不打印
		    free(ptr);
	    }
    };

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

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

相关文章

安卓LayoutParams浅析

目录 前言一、使用 LayoutParams 设置宽高二、不设置 LayoutParams2.1 TextView 的 LayoutParams2.2 LinearLayout 的 LayoutParams 三、getLayoutParams 的使用四、setLayoutParams 的作用五、使用 setWidth/setHeight 设置宽高 前言 先来看一个简单的布局&#xff0c;先用 x…

百元挂耳式耳机哪款好?五款高品质一流机型不容错过

开放式耳机以其独特的不入耳设计&#xff0c;大大提升了佩戴的舒适度。相较于传统的入耳式耳机&#xff0c;它巧妙地避免了对耳朵的压迫&#xff0c;降低了中耳炎等潜在风险。不仅如此&#xff0c;开放式耳机还能让你保持对周边声音的灵敏度&#xff0c;无论是户外跑步还是骑行…

Day08-JavaWeb开发-MySQL(多表查询内外连接子查询事务索引)Mybatis入门

1. MySQL多表查询 1.1 概述 1.2 内连接 -- 内连接 -- A. 查询员工的姓名, 及所属的部门名称(隐式内连接实现) select tb_emp.name, tb_dept.name from tb_emp,tb_dept where tb_emp.dept_id tb_dept.id;-- 起别名 select * from tb_emp e, tb_dept d where e.dept_id d.id…

信息化飞速发展下,源代码防泄密方案该如何选择?常见四种有效方案分享

信息化时代发展迅速&#xff0c;数据防泄露一词也频繁的出现在我们身边。无论企业或政府单位&#xff0c;无纸化办公场景越来越多&#xff0c;数据泄露的时间也层出不穷。例如&#xff1a;世界最大职业中介网站Monster遭到黑客大规模攻击&#xff0c;黑客窃取在网站注册的数百万…

The Sandbox 案例|Web3 项目引领娱乐业的发展

Web3 如何通过 RZR 系列等项目开创娱乐新纪元。 我们已经看到技术和 Web3 如何颠覆金融和银行等行业&#xff0c;然而娱乐业在不断变化的环境中似乎发展滞后。传统的制片厂生态系统、高成本制作以及历史悠久的运作模式一直占据主导地位&#xff0c;而 Web3 项目的出现为创作者提…

CondaHTTPError: HTTP 404 NOT FOUND for url xxx

今天在创建新环境的时候给我报这个错 根据报错内容大概猜测&#xff0c;连接不到清华源&#xff1f; 然后我去清华源那边重新复制了一下配置 点这里跳转清华源 复制这一块东西 然后打开C盘->用户->找到.condarc文件打开 复制粘贴把里面的东西覆盖了就行 然后保存退出…

【excel】统计单元格内数字/字符串的数量

文章目录 一、问题二、步骤&#xff08;一&#xff09;将A1中的数字分解出来&#xff0c;在不同的单元格中显示&#xff08;二&#xff09;统计每个数字出现的个数&#xff08;三&#xff09;去重 三、尾巴 一、问题 单元格中有如下数值&#xff1a;12345234534545&#xff0c…

给excel中某列数值前后分别添加单引号

将这一列的数值&#xff0c;复制到 Sublime Text中&#xff0c;并CtrlA全选内容后&#xff0c;按CtrlH 选中工具栏左上角的 如果要给前面添加符号&#xff08;如单引号&#xff09;&#xff0c;则在Fina查找内容中&#xff0c;填入 ^ 如果要给后面添加符号&#xff08;如单引…

【操作系统】内存管理——地址空间连续内存分配与非连续内存分配

内存管理——地址空间&连续内存分配与非连续内存分配 一、地址空间1.1 计算机存储层次1.2 地址和地址空间1.3 虚拟存储的作用 二、内存分配2.1 静态内存分配2.2 动态内存分配 三、连续内存分配3.1 动态分区分配3.2 伙伴系统&#xff08;Buddy System&#xff09; 四、非连续…

5.1 Java全栈开发前端+后端(全栈工程师进阶之路)-服务端框架-MyBatis框架-相信我看这一篇足够

0.软件框架技术简介 软件框架&#xff08;software framework&#xff09;&#xff0c;通常指的是为了实现某个业界标准或完成特定基本任务的软件组件规范&#xff0c;也 指为了实现某个软件组件规范时&#xff0c;提供规范所要求之基础功能的软件产品。 框架的功能类似于基础设…

cufftPlanMany参数说明

背景 最近在看cufft这个库&#xff0c;传统的cufftPlan3d()这种plan接口逐渐被nvidia舍弃了&#xff0c;说是要用最新的cufftPlanMany&#xff0c;这个函数呢又依赖一个什么Advanced Data Layout(地址)&#xff0c;最终把这个api搞得乌烟瘴气很难理解&#xff0c;为了理解自己…

瓷器三维虚拟展示编辑平台为您量身定制高效实惠的展示方案

在竞争激烈的机械产品行业中&#xff0c;如何脱颖而出、展现产品魅力与企业实力?深圳vr公司华锐视点以其独特的三维动画设计制作服务&#xff0c;为您量身定制全方位的展示方案&#xff0c;让您的机械产品在市场中熠熠生辉。 全方位展示&#xff0c;细节尽收眼底 我们的三维展…

医学论文摘要翻译 中译英哪里比较专业

论文摘要是对论文内容不加注释和评论的简短陈述&#xff0c;需要扼要说明论文的目的、研究方法和最终结论。在发表学术论文时&#xff0c;很多重要刊物会要求作者将文章的摘要翻译成英文。那么&#xff0c;针对医学论文摘要翻译&#xff0c;中译英哪里比较专业&#xff1f; 专…

【论文速读】|针对模糊驱动生成的提示性模糊测试

本次分享论文&#xff1a;Prompt Fuzzing for Fuzz Driver Generation 基本信息 原文作者&#xff1a;Yunlong Lyu, Yuxuan Xie, Peng Chen, Hao Chen 作者单位&#xff1a;腾讯安全大数据实验室、加州大学戴维斯分校 关键词&#xff1a;软件测试, Fuzzing, 自动化Fuzz驱动…

Linux的基础IO:文件系统

目录 学前补充 磁盘的存储结构 OS如何对磁盘的存储进行逻辑抽象 细节内容 文件的增删改查 学前补充 问题&#xff1a;计算机只认二进制&#xff0c;即0、1&#xff0c;什么是0、1&#xff1f; 解释&#xff1a;0、1在物理层面可能有不同的表现&#xff0c;0、1是数字逻辑…

粘土制作的梵高世界;实时自由地转换您的声音Supertone;几秒钟内设计出令人惊叹的LOGO

✨ 1: 梵高的世界 你探索 runwayml #Gen2 过 的风格功能吗&#xff1f;看看这个用粘土制作的梵高作品的视频——就像走进了梵高的双手雕刻的世界。 &#x1f3a8; &#x1f58c;️ 关注更多将经典艺术与现代技术融合的创新方式&#xff01; ✨ 2: Supertone Shift 实时自由…

基于零一万物多模态大模型通过外接数据方案优化图像文字抽取系统

大模型相关目录 大模型&#xff0c;包括部署微调prompt/Agent应用开发、知识库增强、数据库增强、知识图谱增强、自然语言处理、多模态等大模型应用开发内容 从0起步&#xff0c;扬帆起航。 大模型应用向开发路径&#xff1a;AI代理工作流大模型应用开发实用开源项目汇总大模…

深究muduo网络库的Buffer类!!!

最近在学习了muduo库的Buffer类&#xff0c;因为这个编程思想&#xff0c;今后在各个需要缓冲区的项目编程中都可以用到&#xff0c;所以今天来总结一下&#xff01; Buffer的数据结构 muduo的Buffer的定义如下&#xff0c;其内部是 一个 std::vector&#xff0c;且还存在两个…

Pyecharts的编程环境准备

一&#xff0c;准备Python编程环境&#xff1a; Python版本&#xff1a;3.10以上&#xff0c;最高版本3.12 https://www.python.org/ 进入官网&#xff0c;点击downloads—>windows进入下载页面&#xff0c;搜索”3.10.6”找到指定版本&#xff0c;下载并安装64位Installer…

ai智能答题助手,这四款软件让知识触手可及!

在数字化时代&#xff0c;知识的获取变得前所未有的便捷。随着人工智能技术的不断发展&#xff0c;AI智能答题助手应运而生&#xff0c;成为了人们学习、工作和生活中的得力助手。今天&#xff0c;就为大家介绍四款备受欢迎的AI智能答题助手软件&#xff0c;让你感受知识的魅力…