RAII思想

RAII思想的核心定义

RAII(Resource Acquisition Is Initialization)直译为 “资源获取即初始化”,使用局部对象来管理资源的技术称为资源获取即初始化;这里的资源主要是指操作系统中有限的东西如内存、网络套接字等等,局部对象是指存储在栈的对象,它的生命周期是由操作系统来管理的,无需人工介入;是 C++ 特有的、基于对象生命周期的资源管理思想,核心目标是:

  • 资源的生命周期对象的生命周期绑定;
  • 利用 C++ 自动调用析构函数的特性,实现 “资源自动申请、自动释放”,彻底避免资源泄漏。

RAII 的两个关键前提

  1. 栈对象的生命周期:栈上的局部对象(比如函数里定义的普通变量),只要离开作用域(比如函数执行完、{} 代码块结束、抛出异常),编译器会强制自动调用它的析构函数,这是 C++ 语言层面的保障;
  2. 资源绑定:把需要管理的资源(比如堆内存、文件)封装到这个类里,让「对象生→资源生,对象死→资源死」。

RAII 的核心原理

C++ 中,栈上的对象(局部对象)有一个关键特性:当对象离开作用域时,编译器会自动调用其析构函数(无论正常退出还是异常退出)。

RAII 正是利用这一特性,将资源管理封装到类中:

  1. 资源获取(初始化):在类的构造函数中申请资源(如 new 内存、fopen 打开文件);
  2. 资源持有:对象生命周期内,通过类的成员函数提供资源访问接口;
  3. 资源释放:在类的析构函数中释放资源(如 delete 内存、fclose 关闭文件);
  4. 自动执行:对象离开作用域时,析构函数自动调用,资源被无条件释放 —— 即使程序抛出异常,栈展开过程中也会销毁局部对象,析构函数依然会执行。

可视化RAII执行流程

手动管理资源

申请资源

业务逻辑(可能抛异常)

是否抛异常?

释放代码执行不到 → 资源泄漏

手动释放资源

RAII 管理资源

创建 RAII 对象(构造函数申请资源)

业务逻辑(可能抛异常)

是否抛异常?

对象离开作用域 → 析构函数自动释放资源

对象离开作用域 → 析构函数自动释放资源

RAII 是 C++ 的 “资源管家”—— 对象创建时 “接手” 资源,对象销毁时 “交还” 资源,全程自动,无需人工干预。

智能指针

智能指针是栈上的 RAII 对象(而非 “栈上的指针”),这个对象内部持有指向堆内存的裸指针;当智能指针对象离开作用域时,其析构函数会自动调用 delete 释放它管理的堆内存 —— 无论代码正常执行还是抛出异常,释放操作都会执行,从而彻底防止内存泄漏。

C++ 标准库已经提供了成熟的 RAII 实现 ——智能指针,这是 RAII 最核心、最常用的应用,主要有两种:

智能指针 核心特点(RAII 体现) 适用场景
std::unique_ptr 独占式管理资源:一个指针只能管一个资源,不能拷贝,析构时自动 delete 绝大多数场景
std::shared_ptr 共享式管理资源:引用计数,计数为 0 时才 delete,支持拷贝 需要多个指针共享一个资源
unique_ptr
#include <iostream>
#include <memory> // 智能指针的头文件
#include <stdexcept>
using namespace std;

void func() 
{
    // 1. 创建 unique_ptr(RAII 方式管理堆内存)
    unique_ptr<int> p(new int(20)); 
    // 或更安全的写法:make_unique(C++14 及以上)
    // auto p = make_unique<int>(20);
    // 2. 访问资源(和普通指针一样)
    cout << *p << endl; 
    // 3. 模拟异常:资源依然会释放
    throw runtime_error("测试异常"); 
}

int main() {
    try {
        func();
    } catch (const exception& e) {
        cout << e.what() << endl;
    }
    // 无需 delete,p 析构时自动释放内存
    return 0;
}

关键特性

  • unique_ptr 不能拷贝(unique_ptr<int> p2 = p; 会编译报错),避免浅拷贝重复释放;
  • 支持 移动语义(std::move),可以转移资源所有权(比如函数返回 unique_ptr);
  • 体积和普通指针一样,无额外性能开销。
shared_ptr
class Ball
{
    public:
        Ball() { cout << "A ball has been created." << endl; }
        ~Ball() { cout << "A ball has been destroyed." << endl; }
        void bounce() { cout << "The ball is bouncing!" << endl; }
};

int main()
{   
    shared_ptr<Ball> p = make_shared<Ball>();
    cout << p.use_count() << endl; // 1
    shared_ptr<Ball> p2 = p;
    cout << p.use_count() << ' ' << p2.use_count() << endl;// 2 2
    shared_ptr<Ball> p3 = p2;
    cout << p.use_count() << ' ' << p2.use_count() << ' ' << p3.use_count() << endl;// 3 3 3
    // The ball is bouncing!
    p->bounce();
    p2->bounce();
    p3->bounce();
    // reset the pointer one by one
    p.reset();
    cout << p2.use_count() << ' ' << p3.use_count() << ' ' << endl; // 2 2
    p2.reset();
    cout << p3.use_count() << endl; // 1
    p3.reset();
    // Auto deleted Ball : "A ball has been destroyed" 
    return 0;
}

关键特性

  • 内部维护「引用计数」,每拷贝一次计数 +1,每销毁一个指针计数 -1;
  • 只有计数为 0 时,才会调用 delete 释放资源;
  • 注意:避免「循环引用」(比如两个 shared_ptr 互相指向对方),否则计数永远不为 0,会导致内存泄漏
weak_ptr

为了解决上面的shared_ptr循环引用问题,C++还引入了weak_ptr:

weak_ptr 是 C++ 标准库提供的弱引用智能指针,核心特点:

  1. 它是 shared_ptr 的 “辅助工具”,不能单独使用(无法直接访问堆内存);
  2. 持有 weak_ptr 不会增加 shared_ptr 的引用计数(“弱引用” 的含义);
  3. 可以随时从 weak_ptr 转换成 shared_ptr,从而安全访问堆内存。
class B;
class A {
public:
    weak_ptr<B> b_ptr; // 改为 weak_ptr(弱引用)
    ~A() { cout << "A 析构" << endl; }
};
class B {
public:
    weak_ptr<A> a_ptr; // 改为 weak_ptr(弱引用)
    ~B() { cout << "B 析构" << endl; }
};
int main() {
    shared_ptr<A> a = make_shared<A>();
    shared_ptr<B> b = make_shared<B>();
    a->b_ptr = b; // weak_ptr 接收 shared_ptr,计数不增加
    b->a_ptr = a; // weak_ptr 接收 shared_ptr,计数不增加

    // 查看引用计数:都是 1(关键!)
    cout << "a 的计数:" << a.use_count() << endl; // 1
    cout << "b 的计数:" << b.use_count() << endl; // 1
} // main 结束,a/b 销毁,计数减到 0,析构函数执行!

核心变化

  • A 和 B 中不再持有 shared_ptr,而是 weak_ptr
  • weak_ptr 接收 shared_ptr 时,不会增加引用计数(计数始终为 1);
  • main 结束后,a/b 销毁,计数减到 0,A 和 B 的析构函数执行,堆内存正常释放。

weak_ptr 的核心操作

weak_ptr 不能直接解引用(*wp 会报错),必须先转换成 shared_ptr 才能访问资源,核心操作有 3 个:

操作 作用
wp.lock() weak_ptr 转换成 shared_ptr:1. 若资源还存在 → 返回有效的 shared_ptr;2. 若资源已释放 → 返回空的 shared_ptr
wp.expired() 判断所指向的资源是否已释放(计数为 0):true = 已释放,false = 未释放
wp.use_count() 获取对应的 shared_ptr 的引用计数(和 shared_ptruse_count 一致)
int main() 
{
    // 1. 创建 shared_ptr
    shared_ptr<int> sp = make_shared<int>(100);
    // 2. 创建 weak_ptr,关联到 sp
    weak_ptr<int> wp = sp;
    // 3. 查看状态
    cout << "是否过期:" << boolalpha << wp.expired() << endl; // false
    cout << "计数:" << wp.use_count() << endl; // 1
    // 4. 转换成 shared_ptr 访问资源
/* 
shared_ptr 类重载了布尔类型转换运算符(operator bool())—— 这个运算符会让 shared_ptr 对象在需要 bool 值的场景(比如 if 条件)下,自动转换成 bool:
如果 shared_ptr 持有有效资源(指针非空)→ 转换成 true;
如果 shared_ptr 是空的(指针为空,资源已释放)→ 转换成 false。
*/
    if (shared_ptr<int> temp = wp.lock()) { // 资源未过期,temp 有效
        cout << *temp << endl; // 输出 100
    } else {
        cout << "资源已释放" << endl;
    }
    // 5. 释放 sp,资源销毁
    sp.reset(); // 手动重置 shared_ptr,计数减到 0
    cout << "是否过期:" << wp.expired() << endl; // true
    // 6. 再次尝试访问(失败)
    if (shared_ptr<int> temp = wp.lock()) {
        cout << *temp << endl;
    } else {
        cout << "资源已释放" << endl; // 输出这行
    }
    return 0;
}

weak_ptr 的核心特性

  1. 不增加引用计数:这是和 shared_ptr 最核心的区别,也是解决循环引用的关键;
  2. 不管理资源生命周期weak_ptr 不会触发资源释放,只有 shared_ptr 的计数为 0 时才会释放;
  3. 线程安全(基础层面)weak_ptrlock() 操作是原子的,多线程下转换不会出问题;
  4. 不能单独使用:必须依赖 shared_ptr 才能创建,无法直接 make_weak_ptr
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐