手动管理内存,本质是在跟“忘记释放”和“异常打断”两件事较劲。一段代码new了两块内存,中间某个函数调用抛了异常,后面的delete永远执行不到,泄漏就发生了。智能指针解决的就是这个场景——让资源有确定的、不依赖正常流程的释放时机。

目录

1. 资源泄漏的典型场景

2. RAII:把资源绑在对象生命周期上


1. 资源泄漏的典型场景

下面这段代码很直观地展示了问题:Func里new了两个数组,然后调用Divide。如果除零抛异常,后面正常的delete不会被执行。即便用try/catch包住,如果在array2的new时也抛异常,那array1就已经泄漏了——catch里根本不知道array2有没有分配成功。

cpp

void Func() {
    int* array1 = new int[10];
    int* array2 = new int[10];  // 这一行也可能抛异常

    try {
        int len, time;
        cin >> len >> time;
        cout << Divide(len, time) << endl;
    } catch (...) {
        delete[] array1;
        delete[] array2;
        throw;   // 清理后重新抛出
    }

    delete[] array1;
    delete[] array2;
}

手动一层层套try/catch来保释放,代码臃肿不说,逻辑一复杂就容易漏。这个问题不是“程序员不细心”,而是一种结构性缺陷——资源释放依赖正常控制流走到,而异常偏偏打断了控制流。

2. RAII:把资源绑在对象生命周期上

RAII(Resource Acquisition Is Initialization)的思路很简单:在构造函数里获取资源,在析构函数里释放资源。对象在栈上分配,离开作用域时析构函数一定会被调用,不管你是正常return还是异常抛出去。这个“一定”是C++语言保证的,用户不用写哪怕一行try/catch来管释放。

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]; }

private:
    T* _ptr;
};

用这个最简单的SmartPtr改造Func

cpp

void Func() {
    SmartPtr<int> sp1(new int[10]);
    SmartPtr<int> sp2(new int[10]);

    for (size_t i = 0; i < 10; ++i)
        sp1[i] = sp2[i] = i;

    int len, time;
    cin >> len >> time;
    cout << Divide(len, time) << endl;
}
// 无论正常结束还是异常退出,sp1/sp2析构,资源释放

不需要try/catch了。Divide抛异常,栈展开析构sp2再析构sp1,两个数组被delete干净。代码量反而更少。这就是RAII对资源安全的根本价值——把不确定的手动释放变成确定的自动析构

智能指针本质上就是RAII在指针资源上的具体应用。除了自动释放,它还要解决另一个问题:如何像原生指针一样去访问资源?于是有了operator*operator->operator[]等运算符重载。这两点构成了智能指针的两个基本面:资源管理与指针模拟。

RAII并不只用于内存。文件句柄、网络连接、互斥锁,凡是“获取-使用-释放”的资源,都可以用同样的思路管理。等介绍shared_ptr配合自定义删除器时,会看到一个fopen的例子。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐