深入理解C++ RAII

什么是 RAII

RAIIResource Acquisition Is Initialization 的缩写,中文常译为"资源获取即初始化"。它是 C++ 中管理资源、保证异常安全的核心惯用法。

它的核心思想是:

把资源的生命周期绑定到一个栈对象的生命周期上。

具体做法:

  • 构造函数中获取资源(如内存、文件、锁、套接字、句柄等)。

  • 析构函数中释放资源。

  • 使用一个栈对象来持有这个资源。

  • 当栈对象离开作用域时,C++ 会自动调用它的析构函数,从而自动释放资源。

  • 即使发生异常,栈展开(stack unwinding)也会保证已构造的栈对象被析构。

因此,RAII 不仅能防止内存泄漏,也能防止文件未关闭、锁未释放、句柄泄漏等问题。


RAII 的关键点

  1. 资源在构造函数中获取

    • 如果获取失败,可以抛出异常,表示对象构造失败。
  2. 资源在析构函数中释放

    • 析构函数不应抛出异常,否则可能导致程序终止。
  3. 对象通常放在栈上,或由智能指针管理

    • 栈对象离开作用域自动析构。

    • 堆对象可以用 std::unique_ptrstd::shared_ptr 等管理。

  4. 利用 C++ 的确定性析构

    • 与垃圾回收不同,C++ 能确定资源何时释放。

    • 适合管理非内存资源,如文件、互斥锁、网络连接。

  5. 正确处理拷贝和移动

    • 如果资源不可共享,应禁止拷贝,或实现移动语义转移所有权。

    • 否则可能重复释放同一资源。

  6. 标准库中大量使用 RAII

    • std::stringstd::vector 管理内存。

    • std::unique_ptrstd::shared_ptr 管理动态对象。

    • std::lock_guardstd::unique_lock 管理互斥锁。

    • std::fstreamstd::ofstream 管理文件。

    • std::jthread(C++20)管理线程。

cpp 复制代码
#include <iostream>
#include <stdexcept>
#include <cstddef>

class IntArray {
public:
    // 构造函数:获取资源(动态内存)
    explicit IntArray(std::size_t n)
        : size_(n), data_(new int[n]) {
        std::cout << "IntArray: 分配了 " << n << " 个 int\n";
        for (std::size_t i = 0; i < n; ++i) {
            data_[i] = 0;
        }
    }

    // 析构函数:释放资源
    ~IntArray() {
        delete[] data_;
        std::cout << "IntArray: 释放了内存\n";
    }

    // 禁止拷贝,防止两个对象管理同一块内存导致 double free
    IntArray(const IntArray&) = delete;
    IntArray& operator=(const IntArray&) = delete;

    // 简单访问接口
    int& operator[](std::size_t i) {
        return data_[i];
    }

    std::size_t size() const {
        return size_;
    }

private:
    int* data_;
    std::size_t size_;
};

void doSomething() {
    // arr 是栈对象,构造时获取资源
    IntArray arr(5);

    arr[0] = 10;
    arr[1] = 20;

    std::cout << "arr[0] = " << arr[0]
              << ", arr[1] = " << arr[1] << "\n";

    // 模拟异常:即使这里抛出异常,arr 的析构函数也会被调用
    throw std::runtime_error("发生异常");

    // 下面这行不会执行
    std::cout << "这行不会执行\n";
}

int main() {
    try {
        doSomething();
    } catch (const std::exception& e) {
        std::cout << "捕获异常: " << e.what() << "\n";
    }

    std::cout << "程序继续执行\n";
    return 0;
}
相关推荐
泡海椒3 小时前
jquick-pdf 表格实战:动态数据 PDF 报表生成
java·开发语言·pdf
aramae3 小时前
模拟实现memset()(C语言)
c语言·开发语言·后端
hetao17338373 小时前
2026-09-18 hetao1733837 的刷题记录
c++·算法
wuyk5554 小时前
【Socket 进阶之路】第 7 章 IO 多路复用 select & poll 完整实战|多 fd 监听、超时等待、优缺点横向对比
服务器·开发语言·网络·数据库·物联网
stolentime4 小时前
CSP-J2026第一轮试题(附答案解析、markdown源码)
c++·csp
船厂电气自动化ai大模型4 小时前
AI大模型与数学|第81天 课程:正交向量、正交基、格拉姆‑施密特(Gram‑Schmidt)正交化
开发语言·数据结构·人工智能·线性代数·机器学习
Rabitebla4 小时前
【Linux系统编程】 指令(二):一条路径是怎么定位到文件的
linux·c++·笔记·学习·算法
lv__pf4 小时前
seata【实战 msb】
java·开发语言·后端
点纭4 小时前
C 语言 第七章 常用函数(3)
c语言·开发语言·算法·oracle·c#