《C++ 异常机制与智能指针:从原理到实现》

异常的抛出和捕获

throw跟catch

大概就是写一个if条件,如果满足就throw抛出这个异常,但是你的外层要有try,然后就是try会跟catch搭配,在try里面throw出来的东西会被catch捕捉到,写catch也是有讲究的,接受throw传来的形参类型要匹配,如果不知道要捕捉的类型是什么,有一种办法就是可以写catch(...)捕捉最近的throw,这个也是工程的最后一道防线,因为有些实习生写法不正确,就会导致一些异常没有被捕获,那么就会造成程序崩溃。

然后throw也是就近为原则,用最近的catch捕获异常

throw后面的代码不会被执行,也就是栈上的对象会自动析构,只有堆资源需要智能指针,就会导致一些之前申请的资源没有被释放,后面我会将智能指针来解决这种问题

栈展开

异常抛出后,程序暂停当前函数的执行,开始执行与之匹配的catch语句,首先检查throw是否在try内部,如果在,则找对应的catch语句,如果有匹配的,那么就跳到对应的catch语句中去

如果当前函数中没有try/catch语句,或者没有匹配的catch语句,那么退出当前函数,在外层调用函数链中查找,对应的catch会展开,如果到达main函数,依旧没有找到匹配的catch语句,那么程序会调用库里面标准的terminate 终止程序

还有一个就是noexcept

函数参数列表后面加noexcept表示不会抛出异常,啥都不加代表可能会抛出异常

cpp 复制代码
#include<iostream>
using namespace std;
double Divide(int a,int b)
{
	try
	{
		//当b==0时出现异常
		if (b == 0)
		{
			string s("Divide by zero condition!");
			throw s;
		}
		else
		{
			return ((double)a / (double)b);
		}
	}
	catch(int errid)   //上面抛出的异常类型是double,这里接受异常的参数类型是Int,所以这里不匹配,就调用外层的try/catch
	{
		cout << errid << endl;
	}
}

void Func()
{
	int len, time;
	cin >> len >> time;

	try
	{
		cout << Divide(len, time) << endl;
	}

	catch (const char* errmsg) //这个也不匹配,再调用外层
	{
		cout << errmsg << endl;
	}

	cout << __FUNCTION__ << ":" << __LINE__ << "行执⾏" << endl;
}

int main()
{
	while(1)
	{
		try
		{
			Func();
		}
		catch(const string& errmsg)   //还好这里匹配到了,如果到了main函数,异常还没有匹配到,那么程序就会终止
		{
			cout << errmsg << endl;
		}
	}

	return 0;
}

模拟一个"分布式 / 微服务场景下的异常传播 + 重试 + 统一错误处理"流程

cpp 复制代码
class Exception
{
public:
	Exception(const string& errmsg, int id)
		:_errmsg(errmsg)
		, _id(id)
	{
	}

	virtual string what() const
	{
		return _errmsg;
	}

	int getid() const
	{
		return _id;
	}
protected:
	string _errmsg;
	int _id;
};

class SqlException : public Exception
{
public:
	SqlException(const string& errmsg, int id, const string& sql)
		:Exception(errmsg, id)
		, _sql(sql)
	{
	}

	virtual string what() const
	{
		string str = "SqlException:";
		str += _errmsg;
		str += "->";
		str += _sql;
		return str;
	}
private:
	const string _sql;
};

class CacheException
{
public:
	CacheException(const string& errmsg, int id)
		//:Exception(errmsg, id)
	{
	}

	virtual string what() const
	{
		string str = "CacheException:";
		//str += _errmsg;
		return str;
	}
};

class HttpException : public Exception
{
public:
	HttpException(const string& errmsg, int id, const string& type)
		:Exception(errmsg, id)
		, _type(type)
	{
	}

	virtual string what() const
	{
		string str = "HttpException:";
		str += _type;
		str += ":";
		str += _errmsg;
		return str;
	}
private:
	const string _type;
};

void SQLMgr()
{
	if (rand() % 7 == 0)
	{
		throw SqlException("权限不足", 100, "select * from name = '张三'");
	}
	else
	{
		cout << "SQLMgr 调用成功" << endl;
	}
}

void CacheMgr()
{
	if (rand() % 5 == 0)
	{
		throw CacheException("权限不足", 100);
	}
	else if (rand() % 6 == 0)
	{
		throw CacheException("数据不存在", 101);
	}
	else
	{
		cout << "CacheMgr 调用成功" << endl;
	}

	SQLMgr();
}

void HttpServer()
{
	if (rand() % 3 == 0)
	{
		throw HttpException("请求资源不存在", 100, "get");
	}
	else if (rand() % 4 == 0)
	{
		throw HttpException("权限不足", 101, "post");
	}
	else
	{
		cout << "HttpServer调用成功" << endl;
	}

	CacheMgr();
}

void _SeedMsg(const string& s)
{
	if (rand() % 2 == 0)
	{
		throw HttpException("网络不稳定,发送失败", 102, "put");
	}
	else if (rand() % 7 == 0)
	{
		throw HttpException("你已经不是对象的好友,发送失败", 103, "put");
	}
	else
	{
		cout << "发送成功" << endl;
	}
}

void SendMsg(const string& s)
{
	// 发送消息失败,则再重试3次
	for (size_t i = 0; i < 4; i++)
	{
		try
		{
			_SeedMsg(s);
			break;          //如果发送成功,就break
		}
		catch (const Exception& e)
		{
			// 捕获异常,if中是102
			// 捕获异常,else中不是102号错误,则将异常重新抛出
			if (e.getid() == 102)
			{
				// 重试三次以后否失败了,则说明网络太差了,重新抛出异常
				if (i == 3)
					throw;
				cout << "开始第" << i + 1 << "重试" << endl;
			}
			else
			{
				throw;
			}
		}
	}
}


int main()
{
	//while (1)
	//{
	//	try
	//	{
	//		HttpServer();
	//	}
	//	catch (const Exception& e) // 这里捕获基类,基类对象和派生类对象都可以被捕获
	//	{
	//		cout << e.what() << endl; // 多态
	//	}
	//	catch (...)
	//	{
	//		cout << "未知异常" << endl;
	//	}
	//	// ...
	//}
	string str = "xxxxxxxx";
	while (cin >> str)
	{
		try
		{
			SendMsg(str);
		}
		catch (const Exception& e)
		{
			cout << e.what() << endl << endl;
		}
		catch (...)
		{
			cout << "Unkown Exception" << endl;
		}
	}
		

	return 0;
}

智能指针的使用及原理

1.智能指针的使用场景分析

下面程序中我们可以看到,new了以后,我们也delete了,但是因为抛异常导,后面的delete没有得到执行,所以就内存泄漏了,所以我们需要new以后捕获异常,捕获到异常后delete内存,再把异常抛出,但是因为new本⾝也可能抛异常,连续的两个new和下⾯的Divide都可能会抛异常,让我们处理起来很麻烦。智能指针放到这样的场景里面就让问题简单多了。

cpp 复制代码
double Divide(int a, int b)
{
	
		//当b==0时出现异常
		if (b == 0)
		{
			throw "Divide by zero condition!";
		}
		else
		{
			return ((double)a / (double)b);
		}
}

void Func()
{
	// 这⾥可以看到如果发⽣除0错误抛出异常,另外下⾯的array和array2没有得到释放。
	// 所以这⾥捕获异常后并不处理异常,异常还是交给外⾯处理,这⾥捕获了再重新抛出去。
	// 但是如果array2new的时候抛异常呢,就还需要套⼀层捕获释放逻辑,这⾥更好解决⽅案
	// 是智能指针,否则代码太戳了

	int* array1 = new int[10];
	int* array2 = new int[10];  //抛异常呢

	try
	{
		int len, time;
		cin >> len >> time;
		cout << Divide(len, time) << endl;
	}

	catch (...) 
	{
		cout << "delete[]" << array1 << endl;
		cout << "delete[]" << array2 << endl;

		delete[] array1;
		delete[] array2;

		throw;    //异常重新抛出,捕获到什么抛出什么
	}

	cout << "delete[]" << array1 << endl;
	delete[] array1;

	cout << "delete[]" << array2 << endl;
	delete[] array2;
}

int main()
{
		try
		{
			Func();
		}
		catch (const char* errmsg)   
		{
			cout << errmsg << endl;
		}

		catch (const exception& e)
		{
			cout << e.what() << endl;
		}

		catch (...)
		{
			cout << "未知异常" << endl;
		}
		return 0;
	

	return 0;
}

下面写的 SmartPtr 就是一个简化版但结构完整的智能指针,它已经具备了智能指针最核心的特性,后面会完善的

cpp 复制代码
template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{
	}

	~SmartPtr()
	{
		cout << "delete:" << _ptr << endl;
		delete[] _ptr;
	}

	// 重载运算符,模拟指针的行为,方便访问资源
	T& operator*()
	{
		return *_ptr;
	}

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

	T& operator[](size_t i)
	{
		return _ptr[i];
	}

public:
	T* _ptr;
};


double Divide(int a, int b)
{
	if (b == 0)
	{
		string s("1111");
		throw s;
	}
	else
	{
		return ((double)a / (double)b);
	}
}

void Func()
{
	// 正常结束还是异常结束,析构都可以保障new的资源正常释放
	SmartPtr<int> sp1 = new int[10];
	SmartPtr<int> sp2 = new int[10];

	//SmartPtr<int> sp3(sp2);

	sp1[0] = 0;

	int len, time;
	cin >> len >> time;
	cout << Divide(len, time) << endl;
}

int main()
{
	//int* p1 = new int[10];
	//SmartPtr<int> sp1(p1);
	//SmartPtr<int> sp1 = new int[10];

	try
	{
		Func();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	catch (...)
	{
		cout << "未知异常" << endl;
	}

	return 0;
}

3.C++标准库智能指针的使用

什么时候析构

cpp 复制代码
struct Date
{
	int _year;
	int _month;
	int _day;
	Date(int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{}

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


#include<memory>

int main()
{
	auto_ptr<Date> ap1(new Date);
	ap1->_year++;

	// 管理权转移,被拷贝对象悬空
	auto_ptr<Date> ap2(ap1);
	ap2->_year++;
	//ap1->_year++;

	unique_ptr<Date> up1(new Date);
	// 禁止拷贝
	// unique_ptr<Date> up2(up1);

	// 可以移动
	unique_ptr<Date> up2(move(up1));

	// 可以拷贝可以移动,通过底层引用计数实现
	shared_ptr<Date> sp1(new Date);
	shared_ptr<Date> sp2(sp1);
	cout << sp1.use_count() << endl;              //2
	cout << sp2.use_count() << endl;              //2

	// 移动会导致sp1管理的资源被转移,sp1悬空
	shared_ptr<Date> sp3(move(sp1));
	cout << sp1 << endl;                         
	cout << sp2 << endl;
	cout << sp3 << endl;

	if (!sp1)
	{
		cout << "sp1 空" << endl;
	}

	//if (sp2.operator bool())
	if (sp2)
	{
		sp2.reset(); // sp2不再管理,手动释放,但是有其他对象的管理的话,就是减计数
		sp3.reset();
		cout << "sp2 非空" << endl;
	}

	Date* ptr = sp3.get();
	cout << ptr << endl;


	int* p = new int(10);

	std::shared_ptr<int> a(new int(20));
	std::shared_ptr<int> b(a, p);  // alias constructor
	cout << a << endl;
	cout << b << endl;
	cout << a.use_count() << endl;
	cout << b.use_count() << endl;

	return 0;
}

下一个知识点

unique_ptr的删除器是传到类模板,当然构造函数也要传删除器,才有构造函数

shared_ptr的删除器是传到构造函数的形参中去

定制删除器

cpp 复制代码
struct Date
{
	int _year;
	int _month;
	int _day;
	Date(int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{}

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

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

class Fclose
{
public:
	void operator()(FILE* ptr)
	{
		cout << "fclose:" << ptr << endl;
		fclose(ptr);
	}
};

int main()
{
	unique_ptr<Date[]> up1(new Date[10]);
	shared_ptr<Date[]> sp1(new Date[10]);

	// 定制删除器
	shared_ptr<Date> sp2(new Date[10], DeleteArray<Date>());                
	shared_ptr<Date> sp3(new Date[10], [](Date* ptr) {delete[] ptr; });

	unique_ptr<Date, DeleteArray<Date>> up2(new Date[5]);
	auto del = [](Date* ptr) {delete[] ptr; };                 //这里用auto出一个对象,是因为如果下面写成类模板里面一个lambda,函数体一个lambda,虽然看起来一样,但是类型是不一样的
	unique_ptr<Date, decltype(del)> up3(new Date[5], del);     //unique_ptr的类模板也可以传lambda,但是要用decltype确认类型   删除器类型无法默认构造,所以显式传入删除器实例del
	// C++20支持
	unique_ptr<Date, decltype(del)> up4(new Date[5]);

	shared_ptr<FILE> sp5(fopen("test.cpp", "r"), Fclose());
	shared_ptr<FILE> sp6(fopen("test.cpp", "r"), [](FILE* ptr) {
		cout << "fclose:" << ptr << endl;
		fclose(ptr);
		});

	shared_ptr<Date> sp10(new Date(2025,10,12));
	//shared_ptr<Date> sp11 = make_shared<Date>(2025, 10, 12);
	auto sp11 = make_shared<Date>(2025, 10, 12);

	//不支持指针隐式类型转换
	//shared_ptr<Date> sp12 = new Date;
	shared_ptr<Date> sp12(new Date);

	return 0;
}

接下来,自己实现shared_ptr的底层

cpp 复制代码
#include<functional>
namespace shasha
{
	template<class T>
	class shared_ptr
	{
	public:
		/*shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new atomic<int>(1))
		{}

		template<class D>
		shared_ptr(T* ptr, D del)
			: _ptr(ptr)
			, _pcount(new atomic<int>(1))
			, _del(del)
		{}*/

		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))
		{}

		template<class D>
		shared_ptr(T* ptr, D del)
			: _ptr(ptr)
			, _pcount(new int(1))
			, _del(del)
		{
		}

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

		void release()
		{
			// 最后一个管理资源的对象释放
			if (--(*_pcount) == 0)
			{
				//delete _ptr;
				_del(_ptr);
				delete _pcount;
			}
		}

		// sp1 = sp1;
		// sp1 = sp2;
		// sp1 = sp3;
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				release();
				_ptr = sp._ptr;
				_pcount = sp._pcount;
				++(*_pcount);

				_del = sp._del;
			}

			return *this;
		}

		~shared_ptr()
		{
			release();
		}

		T* get() const
		{
			return _ptr;
		}

		int use_count() const
		{
			return *_pcount;
		}

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

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

		T& operator[](int i)
		{
			return _ptr[i];
		}
	private:
		T* _ptr;
		int* _pcount;    //动态
		//atomic<int>* _pcount;

		//D _del;
		std::function<void(T*)> _del = [](T* ptr) {delete ptr; };
	};

	// 不增加引用计数
	template<class T>
	class weak_ptr
	{
	public:
		weak_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;
		}

	private:
		T* _ptr = nullptr;
	};
}

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

	shasha::shared_ptr<Date> sp3(new Date);

	sp1 = sp1;
	sp1 = sp2;
	sp1 = sp3;

	shasha::shared_ptr<Date> sp4;

	// 定制删除器
	shasha::shared_ptr<Date> sp5(new Date[10], [](Date* ptr) {delete[] ptr; });

	return 0;
}

还有个问题:循环引用

cpp 复制代码
// 循环引用

struct ListNode
{
	int _data;
	/*std::shared_ptr<ListNode> _next;
	std::shared_ptr<ListNode> _prev;*/
	shasha::weak_ptr<ListNode> _next;
	shasha::weak_ptr<ListNode> _prev;

	ListNode(int val)
		:_data(val)
	{
}

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

int main()
{
	shasha::shared_ptr<ListNode> n1(new ListNode(1));
	shasha::shared_ptr<ListNode> n2(new ListNode(2));

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

	n1->_next = n2;
	n2->_prev = n1;

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;
	
	return 0;
}
cpp 复制代码
int main()
{
	std::shared_ptr<string> sp1(new string("111111"));
	std::shared_ptr<string> sp2(sp1);
	std::weak_ptr<string> wp = sp1;

	cout << wp.expired() << endl;
	cout << wp.use_count() << endl;

	// sp1和sp2都指向了其他资源,则weak_ptr就过期了
	sp1 = make_shared<string>("222222");
	cout << wp.expired() << endl;
	cout << wp.use_count() << endl;

	sp2 = make_shared<string>("333333");
	cout << wp.expired() << endl;
	cout << wp.use_count() << endl << endl;

	wp = sp1;
	//std::shared_ptr<string> sp3 = wp.lock();
	// 要访问资源,一定lock出一个新的shared_ptr对象
	auto sp3 = wp.lock();
	sp1.reset();

	cout << wp.expired() << endl;
	cout << wp.use_count() << endl;

	*sp3 += "###";
	cout << *sp3 << endl;
}
相关推荐
于指尖飞舞1 小时前
java后端面试题(常用集合极简)
java·开发语言·面试
我星期八休息1 小时前
Linux系统编程—mmap文件映射
java·linux·运维·服务器·数据库·mysql·spring
phltxy2 小时前
Spring AI 智能咨询系统综合实战
java·人工智能·spring
aerror2 小时前
如何解决brew安装编译不过的问题
c++
java1234_小锋2 小时前
Spring Boot 中 Starter 是什么?它的核心规范有哪些?请说明如何自定义一个 Starter。
java·spring boot·后端
良枫2 小时前
自进化 agent:核心模块一任务规划器 Planner
java·服务器·windows
AI人工智能+电脑小能手2 小时前
【大白话说Java面试题 第114题】【并发篇】第14题:说一下悲观锁的优点和缺点?
java·开发语言·面试
让我上个超影吧2 小时前
Claude Code 源码看 Agent 系统设计
java·ai·ai编程
Wonderful U2 小时前
Python+Django实战|企业办公用品申领管理系统:物资入库、库存预警、申领审批、归还登记、损耗统计、供应商对账
android·python·django