C++异常与RAII智能指针笔记

一、异常的抛出和捕获

程序出现问题时,可以通过throw抛出对象引发异常;抛出对象的类型,以及当前调用链,决定由哪个catch块处理。

  • 匹配规则:选中的catch是调用链里类型匹配、且离抛出位置最近的那个。

  • throw执行后,throw后面代码不再执行;程序跳转到匹配的catch。catch可以在当前函数,也可以在上层调用函数。控制权转移到catch,会沿着调用链提前退出函数。

  • 一旦进入异常处理流程,栈上创建的对象都会销毁。

  • 抛出异常对象时,会生成异常对象拷贝;catch处理完后销毁拷贝对象(C++11 后优化,减少拷贝)。

二、栈展开 (stack unwinding)

抛出异常后,暂停当前函数执行,向上寻找匹配catch:

  1. 先检查throw是否在try块内,有匹配catch就跳到catch执行。

  2. 当前函数没有try/catch,或者catch类型不匹配:退出当前函数,继续在外层调用函数查找匹配catch,这个过程就是栈展开。

  3. 一直找到main函数仍无匹配catch:调用库函数terminate终止程序。

  4. 找到匹配catch并处理完毕,catch之后代码继续执行。

三、查找匹配的处理代码

  1. 默认:抛出对象类型和catch类型完全匹配;多个匹配时,选择位置更近的。

  2. 允许少量隐式转换:非常量→常量;数组→数组首元素指针;函数→函数指针;派生类→基类(继承体系中非常常用)。

  3. 若到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 思想,差异主要在拷贝管理。

  1. auto_ptr(C++98,已废弃) 拷贝时转移管理权,被拷贝对象悬空,访问会报错;C++11 之后不推荐,项目禁用。

  2. unique_ptr(C++11) 唯一指针,不支持拷贝,只支持移动。不需要共享资源的场景首选。

  3. shared_ptr(C++11) 共享指针,支持拷贝与移动;底层依靠引用计数实现。

    1. 多个shared_ptr指向同一块资源,引用计数 + 1;

    2. 销毁一个shared_ptr,引用计数 - 1;

    3. 引用计数减到 0 时,释放管理的堆资源。

  4. 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修饰:禁止普通裸指针隐式转换成智能指针。

相关推荐
狗凯之家源码网2 分钟前
淘客口令落地页生成器 PHP 版深度评测
开发语言·php
画绛集美术9 分钟前
本地化部署LLM用于美术史问答:一间美术教室的实践记录
开发语言·php
八解毒剂37 分钟前
【计组】中央处理器CPU
java·开发语言·计算机组成原理
FakeOccupational1 小时前
【电路笔记 信号】DBPSK 波形查找表+脉冲成形(升余弦+根升余弦滤波)
开发语言·笔记
打工仔折腾 AI2 小时前
工业场景下时序库与实时计算一体化架构选型实践对比
java·开发语言·后端·python·性能优化·架构·ai agent 实战
ZealSinger2 小时前
Java虚拟线程上线后瓶颈转移到哪看三层
java·开发语言·firefox
无名猿2 小时前
shared_ptr 完全指南:引用计数、控制块与开销
c++·性能优化·内存管理·标准库·现代c++
weixin_440730503 小时前
函数return、参数、参数组、递归函数、高阶函数等小结
开发语言·python
sugarzhangnotes4 小时前
【无标题】
开发语言·人工智能·python