深入理解C++ RAII

什么是 RAII

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

它的核心思想是:

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

具体做法:

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

  • 在析构函数中释放资源。

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

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

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

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


RAII 的关键点

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

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

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

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

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

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

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

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

  5. 正确处理拷贝和移动

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

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

  6. 标准库中大量使用 RAII

    • std::string、std::vector 管理内存。

    • std::unique_ptr、std::shared_ptr 管理动态对象。

    • std::lock_guard、std::unique_lock 管理互斥锁。

    • std::fstream、std::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;
}
相关推荐
徐小黑ACG3 分钟前
Golang 基础05 结构体struct
开发语言·算法·golang
优橙教育20 分钟前
零基础学AI应用开发要多久?3个月能到什么水平
服务器·开发语言·网络·php
SEO_juper1 小时前
用 Python 写一个 GEO 可见性检查脚本:你的网站现在能被 AI 引用吗
开发语言·人工智能·爬虫·python·seo·外贸独立站
西柚小萌新1 小时前
【LLM&&AI应用开发 八股文】--4.3.Agent智能体(下)
java·开发语言·数据库
北极有牛1 小时前
cpp学习笔记--常量指针
java·开发语言·算法
半杯咖啡半行码1 小时前
C++ 编程与 STL 模板:从泛型编程到内存安全详解
开发语言·c++
caoerzhong2 小时前
JeeWMS 开源 WMS 部署避坑指南:Java 仓库管理系统的环境基线、四类根因与可复现交付
java·开发语言·开源
时间的拾荒人2 小时前
Qt 界面优化:绘图详解 —— 从基础图形到图片处理
开发语言·qt·面试
MayZork2 小时前
Qt 文件操作:QSaveFile 与 QTemporaryFile(含 QTemporaryDir)实战
开发语言·qt
北冥you鱼2 小时前
Go 语言空接口(interface{})使用场景与最佳实践
开发语言·windows·golang