【C++】智能指针【内存泄漏|智能指针原理及使用|RAII】

目录

1、了解内存泄露

1.1 内存泄漏的定义及危害

1.2 内存泄漏分类(了解)

1.3 如何检测内存泄漏(了解)

1.4如何避免内存泄漏

2、智能指针的引出

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

3.1 RAII

3.2 智能指针的原理

3.3 std::auto_ptr

3.4 std::unique_ptr

3.5 std::shared_ptr

3.6 定制删除器(了解)


 

1、了解内存泄露

1.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;
}

1.2 内存泄漏分类(了解)

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

1.3 如何检测内存泄漏(了解)

linux 内存泄漏检测: linux 下几款内存泄漏检测工具
windows 使用第三方工具: VLD 工具说明
其他工具: 内存泄漏工具比较

1.4如何避免内存泄漏

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

2、智能指针的引出

因为内存泄漏的危害,故下列场景针对内存的释放,异常都是格外小心的处理

问题场景一。缺陷:不确定是否要抛异常,但是你都会catch捕获

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
	int* p1 = new int;
	//这种写法的问题:不确定是否要抛异常,但你都catch捕获了
	try
	{
		cout << div() << endl;
	}
	//catch (exception& e)
	//{
	//	delete p1;
	//	throw e;
	//}
	catch (...)
	{//下面这种写法也可以
		//拦截下来先释放,再抛异常
		delete p1;
		throw;
	}
	delete p1;
}
int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

 问题场景二、多个new抛异常。缺陷:不确定哪个对象会new失败

 针对以上问题,引出智能指针


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

3.1 RAII

RAII Resource Acquisition Is Initialization )是一种 利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源 ,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后 对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
  • 不需要显式地释放资源。
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效。
//使用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);
	SmartPtr<int> sp2(new int);
	cout << div() << endl;
	//无论是函数正常结束,还是抛异常,都会导致sp对象的生命周期到了后析构函数释放资源
}

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

3.2 智能指针的原理

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

	SmartPtr<pair<int, int>> sp2(new pair<int, int>);
	sp2->first = 20;
	sp2->second = 30;

	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-> ,具有像指针一样的行为。
注:RAII和智能指针的关系:RAII是一个托管资源的思想,智能指针是依靠这种RAII实现的,(unique_lock/lock_guard也依靠RAII),其 基于RAII思想设计一个类,把需要释放的资源交给类对象,通过对象生命周期来管理,在对象析构时释放资源
潜在的问题:

若在上述代码主函数添加 SmartPtr<int> sp3 = sp1(调用编译器自动生成的拷贝构造:浅拷贝); 则会出现问题,因为指向同一块资源会导致析构两次。

那这里能不能深拷贝?不能!

针对这个问题,解决方案如下:

  • 1、管理权转移 C++98 auto_ptr
  • 2、防拷贝 C++11 unique_ptr
  • 3、引用计数的共享拷贝 C++11 shared_ptr

3.3 std::auto_ptr

C++98 版本的库中就提供了 auto_ptr 的智能指针。 auto_ptr 的实现原理:管理权转移的思想。
下面简化模拟实现了一份a uto_ptr 来了解它的原
// C++98 管理权转移 auto_ptr
//模拟实现库中的auto_ptr
namespace mz
{
	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()
{
	 mz::auto_ptr<int> sp1(new int);
	 //用sp1拷贝sp2后,sp2就指向了sp1原指向的空间,而sp1会被置空(不指向任何空间)
	 mz::auto_ptr<int> sp2(sp1); // 管理权转移(空间的管理权转移)

	 // sp1悬空(因为s1已经指向空了!)
	 *sp2 = 10;
	 cout << *sp2 << endl;
	 cout << *sp1 << endl;
	 return 0;
}

auto_ptr缺陷:sp2 = sp1【赋值重载】或 sp2(sp1)【拷贝构造】场景下sp1就悬空了(nullptr),此时访问sp1就会报错,若不熟悉auto_ptr的特性就会被坑,故我们不推荐用甚至不让用auto_ptr


3.4 std::unique_ptr

C++11 中开始提供更靠谱的 unique_ptr。 unique_ptr 的实现原理:简单粗暴的防拷贝。
下面简化模拟实现了一份 UniquePtr 来了解它的原 理:
// C++11库才更新智能指针实现
// C++11出来之前,boost搞除了更好用的scoped_ptr/shared_ptr/weak_ptr
// C++11将boost库中智能指针精华部分吸收了过来
// C++11->unique_ptr/shared_ptr/weak_ptr
// unique_ptr/scoped_ptr
// 原理:简单粗暴 -- 防拷贝
namespace mz
{
	template<class T>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}
		~unique_ptr()
		{
			if (_ptr)
			{
			cout << "delete:" << _ptr << endl;
			delete _ptr;
			}
		}
		// 像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		unique_ptr(const unique_ptr<T>& sp) = delete;//直接不让拷贝
		unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;//直接不让赋值
	private:
		T* _ptr;
	};
}
int main()
{
 /*mz::unique_ptr<int> sp1(new int);
 mz::unique_ptr<int> sp2(sp1);*/

 std::unique_ptr<int> sp1(new int);
 //std::unique_ptr<int> sp2(sp1);

 return 0;
}

优点:防拷贝,简单粗靠,推荐使用

缺点:如果有需要拷贝的场景,它就无法使用


3.5 std::shared_ptr

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

		shared_ptr(shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _count(sp._count)
		{
			//两个对象的引用计数都++
			++_count;
			++sp._count;
		} 

		~shared_ptr()
		{
			if (--_count == 0 && _ptr)
			{//只有当引用计数为0且_ptr!=nullptr时,才会释放这份资源
				cout << "delete:" << _ptr << endl;//打印地址方便观察
				delete _ptr;
				_ptr = nullptr;
			}
		}

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

	private:
		T* _ptr;
		int _count; //引用计数
	};
}

int main()
{
	mz::shared_ptr<int> sp1(new int);
	mz::shared_ptr<int> sp2(sp1);

	return 0;
}

运行结果:什么都没有(代码中析构了会打印地址),为什么?

因为sp1和sp2是类对象,sp1初始的_count为1,用sp1拷贝sp2后,sp2的_count也变为1,然后sp2和sp1的_count都++变为2,等到要析构时,sp1和sp2的_count--变为1,没变为0,故不析构,所以是因为sp1和sp2各有各的_count,故_count定义为int类型不可以

②、static int_count(不行)

// 引用计数支持多个拷贝管理同一个资源,最后一个析构对象释放资源
namespace mz
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{
			_count = 1;//不能放在列表初始化里,会被认为是初始化,但是函数体内可以
		}

		shared_ptr(shared_ptr<T>& sp)
			:_ptr(sp._ptr)
		{
			++_count;//静态成员变量++
		} 

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//if (this != &sp)
			if (_ptr != sp._ptr)
			{
			}
			return *this;
		}

		~shared_ptr()
		{
			if (--_count == 0 && _ptr)
			{//只有当引用计数为0且_ptr!=nullptr时,才会释放这份资源
				cout << "delete:" << _ptr << endl;//打印地址方便观察
				delete _ptr;
				_ptr = nullptr;
			}
		}

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

	private:
		T* _ptr;
		static int _count; //引用计数
	};

	template<class T>
	int shared_ptr<T>::_count = 0;//成员变量类内定义,类外初始化
}

int main()
{
	mz::shared_ptr<int> sp1(new int);
	mz::shared_ptr<int> sp2(sp1);

	mz::shared_ptr<int> sp3(new int);

	return 0;
}

运行结果:

两块资源只析构了一次?原因如下: 

 

③、int& _count(不行)

 引用是外面传一个来引用计数,每个对象都用这个引用计数。缺点:外面可以改传进来的引用计数,而且容易造成混乱。这是利用外面来控制的,不好!

④、int* _pcount(可行)

// 引用计数支持多个拷贝管理同一个资源,最后一个析构对象释放资源
namespace mz
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_pcount(new int(1))
		{}

		shared_ptr(shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			,_pcount(sp._pcount)
		{
			++(*_pcount);//对同时管理的这块资源的引用计数++
		} 

		//sp1 = sp4
		shared_ptr<T>& operator=(shared_ptr<T>& sp)
		{
			//if (this != &sp)这种写法也可以
			if (_ptr != sp._ptr)//如果管理的不是同一块资源,才会赋值
			{
				//判断是否需要释放旧空间资源
				//若我是最后一个管理资源的对象,则需要释放资源
				if (--(*_pcount) == 0)
				{	//释放旧空间资源
					delete _pcount;
					delete _ptr;
				}
				//管理同一份空间资源
				_ptr = sp._ptr;
				_pcount = sp._pcount;
				++(*_pcount);//引用计数++
			}

			return *this;
		}

		~shared_ptr()
		{
			if (--(*_pcount) == 0 && _ptr)
			{//只有当引用计数为0且_ptr!=nullptr时,才会释放这份资源
				cout << "delete:" << _ptr << endl;//打印地址方便观察
				delete _ptr;
				_ptr = nullptr;

				delete _pcount;
				_pcount = nullptr;
			}
		}

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

	private:
		T* _ptr;
		int* _pcount; //引用计数:有多少个对象一起共享管理资源
	};
}

int main()
{
	mz::shared_ptr<int> sp1(new int);
	mz::shared_ptr<int> sp2(sp1);

	mz::shared_ptr<int> sp3(new int);
	mz::shared_ptr<int> sp4(sp3);
	mz::shared_ptr<int> sp5(sp3);

	return 0;
}

运行结果:

 线程安全问题:

shared_ptr的线程安全分两方面:

1. 智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时 ++ -- ,这个操作不是原子的,引用计数原来是 1 ++ 了两次,可能还是 2.这样引用计数就错 乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数 ++ --是需要加锁的,也就是说引用计数的操作是线程安全的。
2. 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题

 原因:shared_ptr的引用计数是在堆上 

 测试进程安全问题:

修改代码:加锁 (一个线程完事了才能让另一个线程开始)


// 引用计数支持多个拷贝管理同一个资源,最后一个析构对象释放资源
//shared_ptr的拷贝赋值时线程安全问题
//shared_ptr是否是线程安全的,答:注意这里的shared_ptr对象拷贝和析构++/--引用计数
//是否是安全的,库中的实现是安全的
#include<thread>
#include<mutex>

namespace mz
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_pcount(new int(1))
			,_pmtx(new mutex)
		{}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			,_pcount(sp._pcount)
			,_pmtx(sp._pmtx)
		{
			add_ref_count();//对同时管理的这块资源的引用计数++
		} 

		//sp1 = sp4
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{5
			//if (this != &sp)这种写法也可以
			if (_ptr != sp._ptr)//如果管理的不是同一块资源,才会赋值
			{
				//判断是否需要释放旧空间资源
				//若我是最后一个管理资源的对象,则需要释放资源
				release();
				//管理同一份空间资源
				_ptr = sp._ptr;
				_pcount = sp._pcount;
				_pmtx = sp._pmtx;

				add_ref_count();
			}

			return *this;
		}

		void add_ref_count()
		{
			_pmtx->lock();

			++(*_pcount);
			_pmtx->unlock();
		}

		//两个线程若同时去释放,也会出现问题
		void release()
		{
			bool flag = false;
			_pmtx->lock();
			if (--(*_pcount) == 0 && _ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
				delete _pcount;
				_ptr = nullptr;
				_pcount = nullptr;
                //delete _pmtx;//这里不能直接释放锁,因为unlock还没执行呢,故用flag

				flag = true;
			}
			_pmtx->unlock();
			if (flag == true)
			{//要保证资源释放完了,才能释放锁
				delete _pmtx;
				_pmtx = nullptr;
			}
		}

		~shared_ptr()
		{
			release();
		}

		int use_count()
		{
			return *_pcount;
		}

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

	private:
		T* _ptr;
		int* _pcount; //引用计数:有多少个对象一起共享管理资源
		mutex* _pmtx; //互斥锁:为了保护引用计数,管理同一份资源的对象共用一个锁
	};
}
int main()
{
	mz::shared_ptr<int> sp(new int);
	cout << sp.use_count() << endl; //1
	int n = 10000;
	//单纯两个拷贝出现出现问题的概率很小
	/*std::thread t1([&]() {
		mz::shared_ptr<int> sp1(sp);
	});

	std::thread t2([&]() {
		mz::shared_ptr<int> sp2(sp);
	});*/

	//出了作用域sp1和sp2就销毁了(一个{}算一个作用域)

	//若同时进行10000次就会出现线程安全问题(100,1000次都不一定出现问题)
	std::thread t1([&]() {
		for (int i = 0; i < n; ++i)
		{
			mz::shared_ptr<int> sp1(sp);
		}
		});

	std::thread t2([&]() {
		for (int i = 0; i < n; ++i)
		{
			mz::shared_ptr<int> sp2(sp);
		}
		});

	t1.join();
	t2.join();
	cout << sp.use_count() << endl; //1

	return 0;
}

加锁后结果正确: 

 这里为什么要用锁而不用原子操作?

原子操作可以保护计数,但可能没办法保护释放过程,要用原子操作还要改结构,会复杂点

总结shared_ptr: 

优点:引用计数,可以拷贝

缺陷:循环引用(特殊场景下出现)

循环引用分析:
  • 1. node1node2两个智能指针对象指向两个节点,引用计数变成1,无需手动delete
  • 2. node1_next指向node2node2_prev指向node1,引用计数变成2
  • 3. node1node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点
  • 4. 只有node1的_next析构了,node2才释放,只有node2的_prev析构了,node1才释放
  • 5. 但是_next属于node的成员,node1释放了,_next才会析构,而node1_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放。

本质原因: 智能指针对象_prev和_next是属于节点的,而节点是new出来的空间,这块空间被delete了,节点的成员才会被释放。

针对shared_ptr这个缺陷,用weak_ptr来弥补:

解决循环引用方案:

在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了。

因为当sp1->_next = sp2; sp2->_prev = sp1;时weak_ptr的_next和_prev不会增加sp1和sp2的引用计数

//严格来说,weak_ptr不是智能指针,因为他没有RAII资源管理
//专门解决shared_ptr的循环引用问题
//简化版本的weak_ptr实现
namespace mz
{
	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)
		{
			_ptr = sp.get();

			return *this;
		}

		T* get() const
		{
			return _ptr;//获取原生指针
		}

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

		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};
}
struct ListNode
{
	int _data;
	mz::weak_ptr<ListNode> _prev;
	mz::weak_ptr<ListNode> _next;

	~ListNode() { cout << "~ListNode()" << endl; }
};
int main()
{
	shared_ptr<ListNode> sp1(new ListNode);
	shared_ptr<ListNode> sp2(new ListNode);

	cout << sp1.use_count() << endl;
	cout << sp2.use_count() << endl;

	//循环引用
	sp1->_next = sp2; //解决方式:使用wear_ptr,不增加引用计数
	sp2->_prev = sp1;

	cout << sp1.use_count() << endl;
	cout << sp2.use_count() << endl;
	return 0;
}


3.6 定制删除器(了解)

定制删除器就是传一个实现对应释放方式的仿函数对象进去给智能指针,因为智能指针默认析构是用delete,但是delete[]、malloc和文件等场景下,析构就不能用delete那么简单了,故要让我们自己实现个定制删除器

//定制删除器(了解)
#include<memory>
#include<cstdlib>
class A
{
public:
	~A()
	{
		cout << "~A()" << endl;
	}

private:
	int _a1;
	int _a2;
};

//解决:写个仿函数传给智能指针即可完成析构
template<class T>
struct DeleteArry
{
	void operator()(T* pa)
	{
		delete[] pa;
	}
};

struct  Free
{
	void operator()(void* p)
	{
		cout << "Free(p)" << endl;
		free(p);
	}
};

struct Fclose
{
	void operator()(FILE* p)
	{
		cout << "Fclose(p)" << endl;

		fclose(p);
	}
};

int main()
{
	std::shared_ptr<A> sp1(new A);//new出来的对象会正常析构
	//因为智能指针的析构就是delete,没有delete[]等
	//std::shared_ptr<A> sp2(new A[10]);//程序崩溃
	//std::shared_ptr<A> sp3((A*)malloc(sizeof(A)));//程序崩溃
	//std::shared_ptr<FILE> sp4(fopen("text.txt", "w"));//程序崩溃

	//针对特殊的析构,利用仿函数,而不用本来的delete了
	std::shared_ptr<A> sp2(new A[10], DeleteArry<A>());
	std::shared_ptr<A> sp3((A*)malloc(sizeof(A)), Free());
	std::shared_ptr<FILE> sp4(fopen("text.txt", "w"), Fclose());

	return 0;
}

运行结果:


 3.7 lock_guard(补充知识)

 观察下面代码:

引入锁管理守卫:lock_guard

我们模拟实现一个

#include<mutex>

//使用RAII思想设计的锁管理守卫
template<class Lock>
class LockGuard
{
public:
	LockGuard(Lock& lock)
		:_lk(lock)
	{//因为锁不支持拷贝,所以_lk加&就可以解决
		_lk.lock();
	}

	~LockGuard()
	{
		cout << "解锁" << endl;
		_lk.unlock();
	}

	//锁守卫也不允许拷贝和赋值
	LockGuard(LockGuard<Lock>&) = delete;
	LockGuard<Lock>& operator()(LockGuard<Lock>&) = delete;

private:
	Lock& _lk; //注意用引用是因为锁不支持拷贝,那就和外面传进来的锁用一个即可
};

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void f()
{
	mutex mtx;
	LockGuard<mutex> lg(mtx);//无论是否抛异常都会正常解锁
	cout << div() << endl; //div函数有可能抛异常
}

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

	return 0;
}

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

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

相关文章

skynet学习笔记01— skynet开发环境搭建(超详细)与第一个skynet程序

00、参考资料 https://blog.csdn.net/qq769651718/category_7480207.html 01、前置准备 开发所在目录 mhzzjmhzzj-virtual-machine:~/work/skynetStudy$ pwd /home/mhzzj/work/skynetStudy前置准备 mhzzjmhzzj-virtual-machine:~/work/skynetStudy$ sudo apt install lua5…

手动关闭PS中的TopazStudio2的登录窗口

2021 adobe photoshop Topaz Studio 2 不是使用防火墙出站规则&#xff0c;是手动关闭的解决方案 点击社区-切换用户&#xff0c;登录窗口会出现X&#xff0c;可以手动关闭

【flutter no devices】

1.在环境变量增加 ANDROID_HOME 值为&#xff1a;C:\Users\Administrator\AppData\Local\Android\Sdk &#xff08;Android sdk 位置) 2 环境变量的path里面增加2个值&#xff1a; %ANDROID_HOME%\platform-tools %ANDROID_HOME%\tools 3 打开cmd&#xff0c;或者在Android st…

uniapp使用抖音微信自定义组件

tt.vue中使用video-player组件 用到的目录如下&#xff1a; pages.json {"path": "pages/Tabbar/tt/tt","style": {"navigationBarTitleText": "","enablePullDownRefresh": false,// 使用自定义组件"using…

使用R语言构建HTTP爬虫:IP管理与策略

目录 摘要 一、HTTP爬虫与IP管理概述 二、使用R语言进行IP管理 三、爬虫的伦理与合规性 四、注意事项 结论 摘要 本文深入探讨了使用R语言构建HTTP爬虫时如何有效管理IP地址。由于网络爬虫高频、大量的请求可能导致IP被封禁&#xff0c;因此合理的IP管理策略显得尤为重要…

【JAVA学习笔记】63 -坦克大战1.3-敌方发射子弹,击中坦克消失并爆炸,敌人坦克随机移动,规定范围限制移动

项目代码 https://github.com/yinhai1114/Java_Learning_Code/tree/main/IDEA_Chapter18/src/com/yinhai/tankgame1_3 〇、要求 增加功能 1.让敌人的坦克也能够发射子弹(可以有多颗子弹) 2.当我方坦克击中敌人坦克时&#xff0c;敌人的坦克就消失,如果能做出爆炸效果更好. …

人工智能-卷积神经网络

从全连接层到卷积 我们之前讨论的多层感知机十分适合处理表格数据&#xff0c;其中行对应样本&#xff0c;列对应特征。 对于表格数据&#xff0c;我们寻找的模式可能涉及特征之间的交互&#xff0c;但是我们不能预先假设任何与特征交互相关的先验结构。 此时&#xff0c;多层感…

Java锁常见面试题

图片引用自&#xff1a;不可不说的Java“锁”事 - 美团技术团队 1 java内存模型 java内存模型(JMM)是线程间通信的控制机制。JMM定义了主内存和线程之间抽象关系。线程之间的共享变量存储在主内存中&#xff0c;每个线程都有一个私有的本地内存&#xff0c;本地内存中存储了该…

计算机毕业设计java+springboot+vue的旅游攻略平台

项目介绍 本系统结合计算机系统的结构、概念、模型、原理、方法&#xff0c;在计算机各种优势的情况下&#xff0c;采用JAVA语言&#xff0c;结合SpringBoot框架与Vue框架以及MYSQL数据库设计并实现的。员工管理系统主要包括个人中心、用户管理、攻略管理、审核信息管理、积分…

Crypto(9)[MRCTF2020]keyboard

下载题目&#xff0c;看看里面是什么 这是什么鬼&#xff0c;由题目可以获得线索&#xff0c;keyboard,不是键盘吗&#xff0c;然后看了看别人写的wp&#xff0c;发现是九键&#xff0c;有几个数字对应的密文就是第几个字母 比如第一个6&#xff0c;对应的字母是mno&#xff0c…

80个10倍提升Excel技能的ChatGPT提示

你是否厌倦了在使用Excel时感觉像个新手&#xff1f;你是否想将你的技能提升到更高的水平&#xff0c;成为真正的Excel大师&#xff1f;嗯&#xff0c;如果你正在使用ChatGPT&#xff0c;那么成为Excel专家简直易如反掌。 你只需要了解一些最有用的Excel提示&#xff0c;就能在…

龙迅LT8619C HDMI转LVDS/RGB/BT656/BT1120/BT601

LT8619C 描述&#xff1a; Lontium的LT8619C是一款高性能的HDMI/双模式DP接收器芯片&#xff0c;符合HDMI 1.4规范。TTL输出可支持RGB、BT656、BT1120&#xff0c;输出分辨率最多可支持4Kx2K30Hz。为了方便地实现一个多媒体系统&#xff0c;LT8619C支持8通道高质量的I2S音频或…

CSDN每日一题学习训练——Java版(两数相加、二叉树的锯齿形层序遍历、解数独)

版本说明 当前版本号[20231106]。 版本修改说明20231106初版 目录 文章目录 版本说明目录两数相加题目解题思路代码思路补充说明参考代码 二叉树的锯齿形层序遍历题目解题思路代码思路参考代码 解数独题目解题思路代码思路补充说明参考代码 两数相加 题目 给你两个 非空 的…

ARMday02(汇编语法、汇编指令)

汇编语法 汇编文件中的内容 1.伪操作&#xff1a;在汇编程序中不占用存储空间&#xff0c;但是可以在程序编译时起到引导和标识作用 .text .global .glbal .if .else .endif .data .word.... 2.汇编指令&#xff1a;每一条汇编指令都用来标识一个机器码&#xff0c;让计算机做…

【亚马逊云科技产品测评】活动征文|亚马逊云科技AWS之EC2详细测评

引言 &#xff08;授权声明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 Developer Centre, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道&#xff09; 在当前的数字化时代&#xff0c;云服务已…

RK3568平台开发系列讲解(音视频篇)RTMP 推流

🚀返回专栏总目录 文章目录 一、RTMP 的工作原理二、RTMP 流媒体服务框架2.1、Nginx 流媒体服务器2.2、FFmpeg 推流沉淀、分享、成长,让自己和他人都能有所收获!😄 📢目前常见的视频监控和视频直播都是使用了 RTMP、RTSP、HLS、MPEG-DASH、 WebRTC流媒体传输协议等。 R…

Apache Doris (五十二): Doris Join类型 - Broadcast Join

🏡 个人主页:IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 🚩 私聊博主:加入大数据技术讨论群聊,获取更多大数据资料。 🔔 博主个人B栈地址:豹哥教你大数据的个人空间-豹哥教你大数据个人主页-哔哩哔哩视频 目录 1. Broadcast Join原理

虹科示波器 | 汽车免拆检测 | 2017款长安福特翼虎车发动机故障灯异常点亮

一、故障现象 一辆2017款长安福特翼虎车&#xff0c;搭载CAF488WQ9发动机&#xff0c;累计行驶里程约为8.9万km。该车因发动机故障灯异常点亮在其他维修厂检修&#xff0c;维修人员用故障检测仪检测&#xff0c;提示气缸3失火&#xff0c;调换火花塞、点火线圈及喷油器&#xf…

时间序列预测模型实战案例(七)(TPA-LSTM)结合TPA注意力机制的LSTM实现多元预测

论文地址->TPA-LSTM论文地址 项目地址-> TPA-LSTM时间序列预测实战案例 本文介绍 本文通过实战案例讲解TPA-LSTM实现多元时间序列预测&#xff0c;在本文中所提到的TPA和LSTM分别是注意力机制和深度学习模型,通过将其结合到一起实现时间序列的预测&#xff0c;本文利用…

基于DevEco Studio的OpenHarmony应用原子化服务(元服务)入门教程

一、创建项目 二、创建卡片 三、应用服务代码 Index.ets Entry Component struct Index {State TITLE: string OpenHarmony;State CONTEXT: string 创新召见未来&#xff01;;build() {Row() {Column() {Text(this.TITLE).fontSize(30).fontColor(0xFEFEFE).fontWeight(…