C++ ---智能指针详解


前言

在这里插入图片描述


正文开始

一、 为什么需要智能指针?

首先我们来分析下面的程序有没有什么关于内存方面的问题呢?
注意:分析func函数中的问题

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
	int* p1 = new int;// 1、如果p1这里new 抛异常会如何?
	int* p2 = new int;// 2、如果p2这里new 抛异常会如何?
	cout << div() << endl;// 3、如果div调用这里又会抛异常会如何?
	delete p1;
	delete p2;
}
int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

问题分析:上面的问题分析出来我们发现有什么问题?

  1. 在这里抛异常会在main函数中捕捉,不会导致问题.
  2. 在这里抛异常会在main函数中捕捉,但是new p1的空间没有释放,导致内存泄露!
  3. 调用div函数后抛异常会在main函数中捕捉,但是new p1和p2的空间没有释放,导致内存泄露!

二、内存泄漏

2.1 什么是内存泄露?危害是什么?

内存泄漏: 内存泄露指因为疏忽或错误造成程序未能释放已经不再使用的内存情况.内存泄露并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费.
内存泄露的危害: 长期运行的程序出现内存泄露,影响很大,如操作系统、后台服务等等,出现内存泄露会导致响应越来越慢,最终卡死.

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void func()
{
	// 1.内存申请了忘记释放
	int* p1 = (int*)malloc(sizeof(int));
	int* p2 = new int;

	// 2.异常安全问题
	int* p3 = new int[10];

	div(); // 这里div函数抛异常导致delete[] p3 未执行,p3没有被释放
	
	delete[] p3;
}

2.2 内存泄露的分类

C/C++程序中一般关心两种方面的内存泄露:

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

2.3 如何避免内存泄露

  1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放.ps:这个理想状态.但是如果碰上异常时,就注意释放了,还是可能会出问题.需要下一条智能指针来管理才有保证.
  2. 采用PAII思想或者智能指针来管理资源.
  3. 有些公司内部规范使用内部实现的私有内存管理库.这套库自带内存泄露检测的功能选项.

总结:内存泄漏非常常见,解决方案分为两种: 1.事前预防型.如智能指针等. 2. 事后查错型.如泄露检测工具等.

三、智能指针的使用及原理

3.1 RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文字句柄、网络连接、互斥量等等)简单技术.

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,**最后在对象析构的时候释放资源.**借此,实际上把管理一份资源的责任托管给一个对象.

这种做法有两大好处:

  • 不需要显式地释放资源.
  • 采用这种方式,对象所需的资源在其生命周期内始终保持有效.
//使用RAII的思想设计的SmartPtr类
namespace hulu
{
	template<class T>
	class SmartPtr
	{
	public:
		SmartPtr(T* ptr)
			:_ptr(ptr)
		{}
		~SmartPtr()
		{
			cout << "delete[]" << _ptr << endl;
			delete[] _ptr;
		}
	private:
		T* _ptr;
	};
}
double div()
{
	double a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
	hulu::SmartPtr<int> sp1(new int[10]);
	hulu::SmartPtr<int> sp2(new int[10]);
	hulu::SmartPtr<int> sp3(new int[10]);
	hulu::SmartPtr<int> sp4(new int[10]);
	div();

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

不抛出异常的情况

在这里插入图片描述

抛出异常的情况

在这里插入图片描述

3.2 智能指针的原理

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

namespace hulu
{
	template<class T>
	class SmartPtr
	{
	public:
		SmartPtr(T* ptr)
			:_ptr(ptr)
		{}
		~SmartPtr()
		{
			cout << "delete[]" << _ptr << endl;
			//delete[] _ptr;
			delete _ptr;
		}

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

	private:
		T* _ptr;
	};
}

总结智能指针的原理:

  1. RAII特性
  2. 重载operator*和operator->,具有像指针一样的行为!

3.3 std::autoptr

auto_ptr的文档介绍
C++98版本的库中就提供了auto_ptr的智能指针.下面演示的auto_ptr的使用及问题.

auto_ptr的实现原理:管理权转移的思想,下面模拟实现了一份hulu::auto_ptr来了解的它的原理.

namespace hulu
{
	template<class T>
	class auto_ptr
	{
	public:
		auto_ptr() {}
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{
			ptr = nullptr;
		}
		auto_ptr(auto_ptr<T>& sp)
			:_ptr(sp._ptr)
		{
			sp._ptr = nullptr;
		}

		auto_ptr<T>& operator=(auto_ptr<T>& sp)
		{
			//检测是不是自己给自己赋值
			if (this != &sp)
			{
				//释放当前对象的资源
				if (_ptr)
					delete _ptr;
				//转移sp中的资源到当前对象
				_ptr = sp._ptr;
				sp._ptr = nullptr;
			}
			return *this;
		}

		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "delete[]" << _ptr << endl;
				delete _ptr;
				_ptr = nullptr;
			}

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

	private:
		T* _ptr;
	};
}
//C++98 auto_ptr 管理权转移,被拷贝的对象悬空 
int main()
{
	//std::auto_ptr<int> sp1(new int);
	//std::auto_ptr<int> sp2 = sp1;
	 
	hulu::auto_ptr<int> sp1(new int);
	hulu::auto_ptr<int> sp2 = sp1;
	hulu::auto_ptr<int> sp3;
	sp3 = sp1;

	// sp1悬空
	//*sp1 = 10;
	*sp2 = 20;
	return 0;
}

在这里插入图片描述
在这里插入图片描述

3.4 std::unique_ptr

C++11中开始提供更靠谱的unique_ptr

unique_ptr的使用文档

unqiue_ptr的实现原理:简单粗暴的防拷贝,下面简单模拟实现了一份unique_ptr来了解它的原理.

namespace hulu{
	template<class T>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}
		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "delete[]" << _ptr << endl;
				delete _ptr;
				_ptr = nullptr;
			}
		}
		//像指针一样
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}

	private:
		unique_ptr(unique_ptr<T>& sp)= delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& sp)= delete;
	private:
		T* _ptr;
	};
}

在这里插入图片描述

3.5 std::shared_ptr

C++11中开始提供更靠谱的并且支持拷贝的shared_ptr

share_ptr的文档介绍

**shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源.**例如:上课开始前,最后一个进入教室的学生需要把门锁着.

  1. shared_ptr在其内部,给每个资源都维护了一份计数,用来记录该份资源被几个对象共享.
  2. 对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源.
  4. 如果不是0就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了.
// 引用计数支持多个拷贝管理同一个资源,最后一个析构对象释放资源
namespace hulu
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr)
			:_ptr(ptr),
			_pCount(new int(1))
		{}
		~shared_ptr()
		{
			Release();
		}
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr),
			_pCount(sp._pCount)
		{
			(*_pCount)++;
		}
		void Release()
		{
			if (--(*_pCount) == 0 && _ptr)
			{
				cout << "delete[]" << _ptr << endl;
				delete _ptr;
				delete _pCount;
				_pCount = nullptr;
				_ptr = nullptr;
			}
		}
		//sp1 = sp3
		shared_ptr<T>& operator=(shared_ptr<T>& sp)
		{
			 // 检测是否自身赋值
			//if (this != &sp)
			if (_ptr == sp._ptr) // 防止sp1 == sp2
			{
				Release();
				_ptr = sp._ptr;
				_pCount = sp._pCount;
				(*_pCount)++;
			}
			return *this;
		}

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

	private:
		T* _ptr;
		int* _pCount;
	};
}

std::shared_ptr的循环引用问题

struct ListNode
{
	ListNode* _next = nullptr;
	ListNode* _prev = nullptr;
	int _val = 0;
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

int main()
{
	std::shared_ptr<ListNode> p1(new ListNode);
	std::shared_ptr<ListNode> p2(new ListNode);

	p1->_next = p2;
	p2->_prev = p1;

	return 0;
}

在这里插入图片描述
进行编译后报错,显示类型不匹配,这是因为在"p1->_next = p2;"中,p2是std::shared_ptr类型,而p1->_next 的类型我们从结构体中可以看出是ListNode*,所以编译就会报错.

那么我们如何解决如上问题呢?

因为我们要使用智能指针,所以将结构体中的类型进行更改为

struct ListNode
{
	std::shared_ptr<ListNode> _next = nullptr;
	std::shared_ptr<ListNode> _prev = nullptr;
	int _val = 0;
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

编译后我们发现

在这里插入图片描述

p1,p2应该要进行销毁,去调用自己的析构函数,但是我们发现实际上却没有调用,所以一定造成了内存泄露,那么这是为什么呢?

引入循环引用问题:

在这里插入图片描述

在这里插入图片描述

画图分析如下:

在这里插入图片描述

在这里插入图片描述
接下来需要我们引入新的智能指针(std::weak_ptr)

在这里插入图片描述
这个weak_ptr可以接受shared_ptr进行传参,此时这个weak_ptr只负责连接,不负责去进行引用计数,这样就很好的解决了循环引用的问题!

struct ListNode
{
	std::weak_ptr<ListNode> _next;
	std::weak_ptr<ListNode> _prev;

	int _val = 0;
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

int main()
{
	std::shared_ptr<ListNode> p1(new ListNode);
	std::shared_ptr<ListNode> p2(new ListNode);

	p1->_next = p2;
	p2->_prev = p1;

	return 0;
}

在这里插入图片描述

3.6 std::weak_ptr

	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}
		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.Get())
		{}
		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp.Get())
			{
				_ptr = sp.Get();
			}
			return *this;
		}

	private:
		T* _ptr;
	};
	struct ListNode
{
	hulu::weak_ptr<ListNode> _next;
	hulu::weak_ptr<ListNode> _prev;

	int _val = 0;
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

int main()
{
	hulu::shared_ptr<ListNode> p1(new ListNode);
	hulu::shared_ptr<ListNode> p2(new ListNode);


	p1->_next = p2;
	p2->_prev = p1;

	return 0;
}

在这里插入图片描述
weak_ptr不参与指向资源的释放管理!

四、定制删除器

如果不是new出来的对象如何通过智能指针管理呢?其实shared_ptr设计了一个删除器来解决这个问题!

class Date
{
public:
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year = 0;
	int _month = 1;
	int _day = 1;
};

// unique_ptr/shared_ptr 默认释放资源用的delete
int main()
{
	std::unique_ptr<Date> up1(new Date);
	std::unique_ptr<Date> up2(new Date[10]);
	std::unique_ptr<Date> up3((Date*)malloc(sizeof(Date)*10));

	return 0;
}

在这里插入图片描述

如何匹配申请方式去释放呢?

class Date
{
public:
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year = 0;
	int _month = 1;
	int _day = 1;
};

// unique_ptr/shared_ptr 默认释放资源用的delete

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

int main()
{
	std::unique_ptr<Date> up1(new Date);
	std::unique_ptr<Date,DeleteArray<Date>> up2(new Date[10]);
	std::unique_ptr<Date,Free<Date>> up3((Date*)malloc(sizeof(Date)*10));

	return 0;
}

在这里插入图片描述

五、完整代码

#pragma once

namespace hulu
{
	template<class T>
	class SmartPtr
	{
	public:
		SmartPtr(T* ptr)
			:_ptr(ptr)
		{}
		~SmartPtr()
		{
			cout << "delete[]" << _ptr << endl;
			//delete[] _ptr;
			delete _ptr;
			_ptr = nullptr;
		}

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

	private:
		T* _ptr;
	};

	template<class T>
	class auto_ptr
	{
	public:
		auto_ptr() {}
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{
			ptr = nullptr;
		}
		auto_ptr(auto_ptr<T>& sp)
			:_ptr(sp._ptr)
		{
			sp._ptr = nullptr;
		}

		auto_ptr<T>& operator=(auto_ptr<T>& sp)
		{
			//检测是不是自己给自己赋值
			if (this != &sp)
			{
				//释放当前对象的资源
				if (_ptr)
					delete _ptr;
				//转移sp中的资源到当前对象
				_ptr = sp._ptr;
				sp._ptr = nullptr;
			}
			return *this;
		}

		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "delete[]" << _ptr << endl;
				delete _ptr;
				_ptr = nullptr;
			}

		}

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

	private:
		T* _ptr;
	};

	template<class T>
	struct default_delete
	{
		void operator()(T* ptr)
		{
			cout << "delete" << ptr << endl;
			delete ptr;
		}
	};


	template<class T,class D = default_delete<T>>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}
		~unique_ptr()
		{
			if (_ptr)
			{
				/*cout << "delete[]" << _ptr << endl;
				delete _ptr;*/
				D del;
				del(_ptr);
				_ptr = nullptr;
			}
		}
		//像指针一样
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}

	private:
		unique_ptr(unique_ptr<T>& sp)= delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& sp)= delete;
	private:
		T* _ptr;
	};

	template<class T, class D = default_delete<T>>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr)
			:_ptr(ptr),
			_pCount(new int(1))
		{}
		~shared_ptr()
		{
			Release();
		}
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr),
			_pCount(sp._pCount)
		{
			(*_pCount)++;
		}
		void Release()
		{
			if (--(*_pCount) == 0 && _ptr)
			{
				/*cout << "delete[]" << _ptr << endl;
				delete _ptr;*/
				D del;
				del(_ptr);
				delete _pCount;
				_pCount = nullptr;
				_ptr = nullptr;
			}
		}
		//sp1 = sp3
		shared_ptr<T>& operator=(shared_ptr<T>& sp)
		{
			 // 检测是否自身赋值
			//if (this != &sp)
			if (_ptr == sp._ptr) // 防止sp1 == sp2
			{
				Release();
				_ptr = sp._ptr;
				_pCount = sp._pCount;
				(*_pCount)++;
			}
			return *this;
		}

		//像指针一样
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		T* Get()const
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pCount;
	};

	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}
		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.Get())
		{}
		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp.Get())
			{
				_ptr = sp.Get();
			}
			return *this;
		}

	private:
		T* _ptr;
	};
}

六、C+11和boost中智能指针的关系

  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中实现的.

总结

(本章完)

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
【为什么还需要学习C++?】 你是否接触很多语言,但从来没有了解过编程语言的本质?你是否想成为一名资深开发人员,想开发别人做不了的高性能程序?你是否经常想要窥探大型企业级开发工程的思路,但苦于没有基础只能望洋兴叹? 那么C++就是你个人能力提升,职业之路进阶的不二之选。【课程特色】 1.课程共19大章节,239课时内容,涵盖数据结构、函数、类、指针、标准库全部知识体系。2.带你从知识与思想的层面从0构建C++知识框架,分析大型项目实践思路,为你打下坚实的基础。3.李宁老师结合4大国外顶级C++著作的精华为大家推出的《征服C++11》课程。【学完后我将达到什么水平?】 1.对C++的各个知识能够熟练配置、开发、部署;2.吊打一切关于C++的笔试面试题;3.面向物联网的“嵌入式”和面向大型化的“分布式”开发,掌握职业钥匙,把握行业先机。【面向人群】 1.希望一站式快速入门的C++初学者; 2.希望快速学习 C++、掌握编程要义、修炼内功的开发者; 3.有志于挑战更高级的开发项目,成为资深开发的工程师。 【课程设计】 本课程包含3大模块基础篇本篇主要讲解c++的基础概念,包含数据类型、运算符等基本语法,数组、指针、字符串等基本词法,循环、函数、类等基本句法等。进阶篇本篇主要讲解编程中常用的一些技能,包含类的高级技术、类的继承、编译链接和命名空间等。提升篇:本篇可以帮助学员更加高效的进行c++开发,其中包含类型转换、文件操作、异常处理、代码重用等内容。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

拾至灬名瑰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值