异常和智能指针

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

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;
}
### 异常处理机制 C++中的异常处理机制是一种用于处理程序运行时错误的技术,它允许程序在遇到错误时抛出一个异常,并在适当的层级捕获并处理这个异常异常处理的基本结构包括`try`、`catch``throw`关键字。 - **try块**:用于包裹可能抛出异常的代码。 - **catch块**:用于捕获并处理由`try`块中抛出的异常。 - **throw表达式**:用于抛出一个异常。 例如,可以使用`try`块来包裹可能抛出异常的代码,并使用`catch`块来捕获并处理这些异常: ```cpp try { // 可能抛出异常的代码 throw std::runtime_error("An error occurred"); } catch (const std::runtime_error& e) { // 处理异常 std::cerr << "Caught exception: " << e.what() << std::endl; } ``` 在抛出异常时,最好使用值传递而不是引用传递,以避免引用无效对象的问题。如果在函数中抛出异常,应确保函数的资源已正确释放,以避免内存泄漏等问题[^3]。 ### 类型转换函数 C++提供了几种类型转换操作符,包括`static_cast`、`dynamic_cast`、`const_cast``reinterpret_cast`,它们各自适用于不同的场景。 - **static_cast**:用于基本数据类型之间的转换,以及具有继承关系的类指针/引用之间的转换。 - **dynamic_cast**:主要用于多态类型的转换,可以在运行时检查转换是否有效。 - **const_cast**:用于添加或移除变量的`const`属性。 - **reinterpret_cast**:用于低级别的类型转换,通常用于将一种类型的指针转换为另一种完全不同类型的指针。 例如,`static_cast`可以用来转换基本数据类型: ```cpp int i = 10; double d = static_cast<double>(i); // 将int转换为double ``` 而`dynamic_cast`则常用于基类派生类之间的指针或引用转换: ```cpp Base* base = new Derived(); Derived* derived = dynamic_cast<Derived*>(base); if (derived) { // 转换成功 } ``` ### 使用智能指针管理动态内存 智能指针C++11引入的一种自动内存管理工具,旨在帮助开发者更好地管理动态分配的内存。常见的智能指针有`std::unique_ptr`、`std::shared_ptr``std::weak_ptr`。 - **std::unique_ptr**:独占所有权的智能指针,不能复制,只能移动。 - **std::shared_ptr**:共享所有权的智能指针,内部维护一个引用计数,当最后一个`shared_ptr`被销毁时,其所指向的对象会被自动删除。 - **std::weak_ptr**:不拥有所有权的智能指针,用于解决`shared_ptr`循环引用的问题。 智能指针的初始化通常使用`new`运算符返回的指针,并且由于智能指针内部的构造函数是`explicit`,不能进行隐式转换,因此必须使用直接初始化: ```cpp #include <memory> #include <iostream> class Person { public: ~Person() { std::cout << "Person 析构函数" << std::endl; } }; int main() { Person* person1 = new Person(); std::shared_ptr<Person> sharedPtr1(person1); // 智能指针帮你释放堆区开辟的内存 Person* person2 = new Person(); std::shared_ptr<Person> sharedPtr2(person2); return 0; } // main函数弹栈会释放所有的栈成员,sharedPtr1sharedPtr2执行对应的析构函数 ``` 通过使用智能指针,可以有效地避免内存泄漏,并且简化了内存管理的复杂性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

通过全部用例

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

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

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

打赏作者

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

抵扣说明:

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

余额充值