异常的抛出和捕获
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;
}