一、异常的抛出和捕获
程序出现问题时,可以通过throw抛出对象引发异常;抛出对象的类型,以及当前调用链,决定由哪个catch块处理。
-
匹配规则:选中的
catch是调用链里类型匹配、且离抛出位置最近的那个。 -
throw执行后,throw后面代码不再执行;程序跳转到匹配的catch。catch可以在当前函数,也可以在上层调用函数。控制权转移到catch,会沿着调用链提前退出函数。 -
一旦进入异常处理流程,栈上创建的对象都会销毁。
-
抛出异常对象时,会生成异常对象拷贝;
catch处理完后销毁拷贝对象(C++11 后优化,减少拷贝)。
二、栈展开 (stack unwinding)
抛出异常后,暂停当前函数执行,向上寻找匹配catch:
-
先检查
throw是否在try块内,有匹配catch就跳到catch执行。 -
当前函数没有
try/catch,或者catch类型不匹配:退出当前函数,继续在外层调用函数查找匹配catch,这个过程就是栈展开。 -
一直找到
main函数仍无匹配catch:调用库函数terminate终止程序。 -
找到匹配
catch并处理完毕,catch之后代码继续执行。
三、查找匹配的处理代码
-
默认:抛出对象类型和
catch类型完全匹配;多个匹配时,选择位置更近的。 -
允许少量隐式转换:非常量→常量;数组→数组首元素指针;函数→函数指针;派生类→基类(继承体系中非常常用)。
-
若到
main还没有匹配异常处理器,程序终止。一般会在main写catch(...)捕获任意类型异常,但无法得知具体错误信息。
四、异常规范(异常说明)
用来标注函数会不会抛出异常,方便调用者理解。
-
C++98:函数参数列表后
throw()表示不抛异常;throw(类型1,类型2...)列出可能抛出的异常类型。写法繁琐,实践很少使用。 -
C++11:简化,
noexcept修饰函数代表不会抛出异常;不写则代表可能抛出异常。编译器不会在编译期校验
noexcept:就算函数内部有throw或者调用可能抛异常的函数,编译依旧可以通过(部分编译器告警)。 如果标记noexcept的函数抛出异常,程序直接调用terminate终止。 -
noexcept(表达式)还可以作为运算符:检测表达式是否可能抛异常,返回false可能抛,返回true不会抛。 示例:
vector<int> v;
v.size(); // noexcept,不会抛异常
v.push_back(1); // 可能抛异常(内存分配失败)
五、RAII 和智能指针设计思路
RAII(Resource Acquisition Is Initialization) :资源获取即初始化。 核心思想:利用对象生命周期管理资源(堆内存、文件句柄、网络连接、互斥锁等)。
-
获取资源时,交给一个对象;资源的访问交给对象控制。
-
对象生命周期内资源保持有效。
-
对象析构的时候自动释放资源。
好处:正常 return,或者异常导致栈展开,都会调用析构函数,保证资源释放,杜绝内存泄漏。
简易智能指针示例:
template<class T>
class SmartPtr
{
public:
SmartPtr(T* ptr)
:_ptr(ptr)
{}
~SmartPtr()
{
cout << "delete:" << _ptr << endl;
delete[] _ptr;
}
public:
T* _ptr;
};
void Func()
{
SmartPtr<int> sp1 = new int[10];
SmartPtr<int> sp2 = new int[10];
int len, time;
cin >> len >> time;
cout << Divide(len, time) << endl;
}
哪怕Divide抛出异常,栈展开时sp1 sp2析构,自动释放 new 出来的数组。
六、C++ 标准库智能指针(<memory>头文件)
除
weak_ptr外,其余都遵循 RAII 思想,差异主要在拷贝管理。
-
auto_ptr(C++98,已废弃) 拷贝时转移管理权,被拷贝对象悬空,访问会报错;C++11 之后不推荐,项目禁用。
-
unique_ptr(C++11) 唯一指针,不支持拷贝,只支持移动。不需要共享资源的场景首选。
-
shared_ptr(C++11) 共享指针,支持拷贝与移动;底层依靠引用计数实现。
-
多个
shared_ptr指向同一块资源,引用计数 + 1; -
销毁一个
shared_ptr,引用计数 - 1; -
引用计数减到 0 时,释放管理的堆资源。
-
-
weak_ptr(C++11) 弱指针,不参与 RAII,不增加引用计数;专门用来解决
shared_ptr循环引用造成的内存泄漏。
智能指针补充要点
-
默认析构使用
delete释放资源;如果不是new出来的资源交给智能指针,析构会崩溃。可以自定义删除器,修改释放逻辑。 -
unique_ptr、shared_ptr支持数组版本:unique_ptr<Date[]> up1(new Date[5]); -
make_shared<T>(args):直接构造资源对象,返回shared_ptr<T>,推荐优先使用。 -
shared_ptr/unique_ptr支持operator bool:智能指针对象可以直接if(sp)判断是否管理有效资源。 -
构造函数使用
explicit修饰:禁止普通裸指针隐式转换成智能指针。