资源 (resource)#
资源是一种概念, 意指某种需要预先获取、之后释放的东西, 例如文件、内存等.
C 语言打开文件、关闭文件#
1#include <cstdio> // for fopen, fclose
2using namespace std;
3
4FILE* file = fopen("text.txt", "w"); // 以写入方式 (write) 打开文件
5/* 对打开的文件进行操作 */
6fclose(file); // 关闭文件, 即释放文件资源
所有权 (ownership)#
如果一个对象须负责释放某种资源, 则称该对象具有该资源的所有权.
1FILE* file = fopen("text.txt", "w");
2fclose(file); // file 须负责释放文件资源, 它具有该资源的所有权
3
4int value = 0;
5int* pointer = &value; // pointer 不需要负责释放 value, 只是引用, 不具有所有权
如果具有所有权的对象没有尽责地释放资源, 将会发生非常恐怖的事情. 例如 file
占用了 text.txt
文件, 如果不进行释放, 则意味着往后再也没有对象可以对 text.txt
文件进行操作.
// 程序 A
int main() {
FILE* file = fopen("text.txt", "w");
} // 离开 main 时, file 被析构, 但是它指向的文件资源没有被释放
// 程序 B
int main() {
FILE* file = fopen("text.txt", "w"); // 错误: 文件被占用!
}

文件被占用#
作用域限定的资源管理 (scope-bound resource management)#
你需要记住哪个对象具有哪些资源的所有权, 并在对象析构之前记得释放资源, 这太累了!
让我们想想, 有没有一种 "资源" 是自动就完成释放的?
1int main() {
2 int value = 0; // 还记得对象的定义吗? 它占有内存资源!
3} // value 被析构, 占有的内存资源随之释放
对象析构时, 它本身的内存 "资源" 被自动释放.
让我们再想想, 有没有一种方法定义对象析构时的行为?
1class Widget {
2 public:
3 ~Widget() { std::cout << "析构函数被调用!\n" }
4};
所以我们可以将文件资源包装为一个类, 构造函数时获取文件资源, 析构函数时释放文件资源:
1#include <cstdio>
2using namespace std;
3
4class File {
5 public:
6 // ↓ 对 file_ 成员初始化
7 File(char const* file_path) : file_{fopen(file_path, "w")} {}
8 ~File() { fclose(file_); }
9 // ...
10
11 private:
12 FILE* file_;
13};
14
15int main() {
16 File file("text.txt");
17} // file 析构时自动释放 text.txt 文件
那时我常用这样的方式解释有关概念, 有一个构造函数, 它建立其他成员函数进行操作的环境基础; 另有一个析构函数来销毁这个环境, 并释放它以前获得的所有资源.
—— Bjarne Stroustrup《The Design and Evolution of C++》
备注
这种技术通常称为 "资源获取即初始化" (Resource Acquisition is Initialization, RAII), 但这名字并没有表现析构函数的作用, Bjarne Stroustrup 本人也多次表示自己取了个坏名字, 只是由于广泛流传而难以纠正.
所以此处选用了 Kate Gregory 在 Naming is Hard: Let's Do Better - Kate Gregory - CppCon 2019 中的命名.
一个教学的黑点#
