Documents

Blog

Raii

Notes on safer resource management in C++.

Published year: 2024

这是一系列关于C++的学习笔记。

著名的C++内存安全

2019年,微软发布了一篇著名的文章,A proactive approach to more secure code,主要结论是有大概70%的安全漏洞来自于不安全的内存。那么作为“现代”的程序员,怎样可以不容易Shoot yourself in the foot呢?同时,因为exception的设计,程序运行的path有的时候不可预测,有的时候很难管理。如果再throw exception的时候,资源没有被释放,那么就会造成资源泄漏。

RAII

两个个人理解的重点:

  1. 应该基于内存管理更少的自由。
  2. 应该基于资源的所属权做出明确的划分。

由此引出了对于有 明确生命周期(invariant)的资源的管理方式:RAII,即Resource Acquisition Is Initialization。

一个简单的例子

RAII适合管理有明确获取(acquire)和释放(release)的资源,比如文件句柄、内存、锁等。下面是一个简单的例子:

手动管理文件

#include <iostream>
#include <fstream>

int main() {
    FILE* file = fopen("example.txt", "w");
    some_complex_function_that_may_throw_exception();
    file << "Hello, RAII!";
    file.close(); // release
    return 0;
}

可以看到,如果在some_complex_function_that_may_throw_exception中抛出了异常,那么file.close()就不会被调用,造成资源泄漏。

使用RAII管理文件


#include <iostream>
#include <fstream>

class File {
public:
    File(const std::string& filename) : file(filename) {}
    ~File() { file.close(); }
    std::ofstream& get() { return file; }
private:
    std::ofstream file;
};

int main() {
    File file("example.txt");
    some_complex_function_that_may_throw_exception();
    file.get() << "Hello, RAII!";
    return 0; // 资源会在File析构的时候被释放
}

另一个lock_guard的例子

RAII与其说是一种模式,不如说是一种思想。STL中已经存在了很多RAII的实现,比如std::lock_guard,它的构造函数会lock一个mutex,析构函数会unlock这个mutex。这样就可以保证在任何情况下,mutex都会被正确释放。

#include <iostream>
#include <mutex>

std::mutex mtx;

void print_block(int n, char c) {
    std::lock_guard<std::mutex> lck(mtx); // acquire lock
    for (int i=0; i<n; ++i) { std::cout << c; }
    std::cout << '\n';
    // automatically release lock
}

int main() {
    std::thread th1(print_block,50,'*');
    std::thread th2(print_block,50,'$');

    th1.join();
    th2.join();

    return 0;
}

Smart Pointers

另一个C++允许你做的蠢事是使用new和delete来手动管理堆上的内存。

别这么做。

C++11引入了std::unique_ptr和std::shared_ptr,它们是RAII的实现,可以帮助你管理堆上的内存。

#include <iostream>
#include <memory>

int main() {
    std::unique_ptr<int> p1(new int(5));
    std::cout << *p1 << std::endl;
    return 0; // p1会在main函数结束的时候被释放
}

References

  1. Stanford CS106L

  2. ISO C++ Core Guidelines

  3. Smart Pointers

© 2026 Ed Zou. All rights reserved.

Built with SvelteKit and Codex.