异常和智能指针

本文探讨了C++异常处理中的内存泄漏问题,介绍了如何通过智能指针(如unique_ptr和shared_ptr)来避免内存泄漏,同时提到了定制删除器的概念。文章详细讲解了异常的概念、抛出与捕获机制,以及RAII技术在资源管理中的应用。
摘要由CSDN通过智能技术生成

c语言处理错误的方式

1,assert(),断言的方式,直接终止程序
2,errno,返回错误码的方式,并且终止程序。
终止程序是非常严重的处理结果,如果是轻微的错误,对于不终止程序,又能的得到错误呢?

异常的概念

当一个函数发现自己无法处理的错误时就可以抛出异常(并不会终止程序),让函数的直接或间接的调用者处理这个错误。

throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
catch: 在您想要处理问题的地方,通过异常处理程序捕获异常.catch 关键字用于捕获异
常,可以有多个catch进行捕获。
try: try 块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch 块

int main()
{
	try
	{
		exception e("error"); //这是异常类   
		throw  e;
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	catch (int i)
	{
		cout << " i " << endl;
	}
	catch (double d)
	{
		cout << d << endl;
	}
	catch (...)  //可捕获任意类型的异常
	{
		cout << "未知异常" << endl;
	}

	cout << "hahaha" << endl;  //异常处理后程序不终止
	return 0;
}

可以抛出的派生类对象,使用基类捕获,这个在实际中非常实用. 通过多态去实现的。并且重写虚函数。
这个具体的就不实现了。

被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个
抛出异常后,会沿着栈展开去匹配catch,如果到达main函数的栈,没有匹配的,则终止程序。
异常是可以重新抛出的。直接throw;
另外catch捕获的是临时对象的引用,因为出函数值就销毁了

异常安全的问题:
不要在构造函数和析构函数中间去抛异常,因为很容易导致不完全初始化和资源未清理的问题。
在new和delete中抛出了异常,导致内存泄漏,在lock和unlock之间抛出了异常导致死锁

异常的总结

C++异常的优点:
相比错误码的方式,可以精准定位错误的位置,展示错误信息。
很多的第三方库都包含异常,比如boost、gtest、gmock等等常用的库,那么我们使用它们也需要使用异常
部分函数使用异常更好处理,比如构造函数没有返回值,不方便使用错误码方式处理。

缺点:
对于抛异常容易导致程序执行流乱跳,不方便调试理解程序。
C++没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常安全问题
C++标准库的异常体系定义得不好,导致大家各自定义各自的异常体系,非常的混乱。

抛异常极易引起的内存泄漏问题

对于抛异常,很容易导致程序出现内存泄漏。什么是内存泄漏呢?
内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内
存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对
该段内存的控制
,因而造成了内存的浪费。

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;
	int* p2 = new int;
	cout << div() << endl;
	delete p1;
	delete p2;
}


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

如何解决这类问题呢?

智能指针的引入

RAII是一种利用对象生命周期来控制程序资源的简单技术。
对象构造的时候去申请资源,
对象析构的时候去释放资源。

	template <class T>
	class smartptr
	{
	public:
		smartptr(T* ptr)
			:_ptr(ptr)
		{}

		~smartptr()
		{
			cout << "~smartptr" << endl;  //这里打印确认一下是否正确析构
			delete _ptr;
			_ptr = nullptr;
		}

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

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

		T* Getptr()
		{
			return _ptr;
		}

	private:
		T* _ptr;
	};

auto_ptr,c++委员会负向的神操作,被吐槽了很久。
它的主要功能是资源的管理权,让被拷贝的对象悬空
在这里插入图片描述

template <class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}

		auto_ptr()
		{
			cout << "~auto_tptr" << endl;  //这里打印确认一下是否正确析构
			delete _ptr;
			_ptr = nullptr;
		}

		//sp2(sp1)
		auto_ptr(const auto_ptr<T>& sp)
			:_ptr(sp._ptr)
		{
			sp._ptr = nullptr;
		}

		//sp2 = sp1
		auto_ptr<T>& operator=(auto_ptr<T>& sp)
		{
			if (this->_ptr != sp._ptr)  //防止下自己给自己赋值
			{
				if (_ptr)
					delete _ptr; // 释放当前的资源,不释放就会有资源泄漏的问题

				_ptr = sp._ptr;
				sp._ptr = nullptr;
			}

			return *this;
		}

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

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

		T* Getptr()
		{
			return _ptr;
		}

	private:
		T* _ptr;
	};

unique_ptr 独一无而的指针,禁止拷贝和赋值。 (boost库,准标准库里面有个scoped_ptr和这个一样)

template <class T>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}

		unique_ptr()
		{
			cout << "~unique_ptr" << endl;  //这里打印确认一下是否正确析构
			delete _ptr;
			_ptr = nullptr;
		}

		unique_ptr(const unique_ptr<T>&) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;

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

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

		T* Getptr()
		{
			return _ptr;
		}

	private:
		T* _ptr;
	};

shared_ptr ,共享指针。 支持拷贝和赋值,通过引用计数的方式。缺点是循环引用的问题。
// 引用计数支持多个拷贝管理同一个资源,最后一个析构对象释放资源
如何去解决循环引用的问题呢? weak_ptr,它仅仅是访问资源,不去管理资源。
本质就是为辅助shared_ptr解决循环引用问题的。

	template <class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_count(new int(1))
		{}

		//sp2(sp1)
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			,_count(sp._count)
		{
			(*_count)++;
		}

		//sp2 = sp1   , sp2管理的资源--,sp1管理的++
		shared_ptr<T>& operator=(shared_ptr<T>& sp)
		{
			if (this->_ptr != sp._ptr)
			{
				Release();
				_ptr = sp._ptr;
				_count = sp._count;
				(*_count)++;
			}

			return *this;
		}

		~shared_ptr()
		{
			Release();
		}

		void Release()
		{
			if (--(*_count) == 0 && _ptr)
			{
				cout << "~shared_ptr" << endl; //可以通过析构次数去判断写的正确性
				delete _ptr;
				delete _count;
				_ptr = nullptr;
			}
		}

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

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

		T* Getptr() const
		{
			return _ptr;
		}

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


	template <class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}

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

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

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

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

		T* Getptr()
		{
			return _ptr;
		}

	private:
		T* _ptr;
	};

在这里插入图片描述

在这里插入图片描述

定制删除器(浅浅的了解一下)

为什么存在定制删除器呢?
unique_ptr和shared_ptr默认是用delete去释放资源的,对于申请数组空间呢?对于malloc呢?
这些都会存在一些问题,所以这里引出了定制删除器,本质是借助仿函数来完成的

template <class T, class D = default_delete<T>> class unique_ptr;
template <class U, class D> shared_ptr (U* p, D del);

我们可以看到unique_ptr是在类的模板参数中指出。
shared_ptr在类的构造函数中支持

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

template<class T>
struct Free
{
	void operator()(T* ptr)
	{
		free(ptr);
	}
};

int main()
{
	chen::unique_ptr<int,DeleteArray<int>> sp1(new int[10]);
	chen::unique_ptr<int, Free<int>> sp2((int*)malloc(sizeof(40)));
    
    std::shared_ptr<int> sp3(new int[10], [](int* ptr) {delete[] ptr; });
	std::shared_ptr<int> sp3(new int[10], DeleteArray<int>());
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

通过全部用例

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

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

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

打赏作者

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

抵扣说明:

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

余额充值