29 December 2021

C++ 核心指南目录

P.8 Don’t leak any resources

理由

如果系统长期运行的,哪怕是没有及时释放的很小的资源,也有可能耗尽系统的资源。所以,对于长期运行的系统,切不可泄漏资源。另外,及时释放系统资源也是一种负责任的编程态度。

例子

void f(char* name)
{
    FILE* input = fopen(name, "r");
    // ...
    if (something) return;
    // bad: if something == true, a file handle is leaked
    // ...
    fclose(input);
}

以上例程中,如果 something 为真,函数直接返回了,没有及时关闭 input 文件结构体,产生资源泄漏。

应该使用 RAII 模式。RAII = Resource Acquisition Is Initialization 意思是分配资源的时候初始化,资源使用结束后,自动释放。

void f(char* name)
{
    ifstream input {name};
    // ...
    if (something) return;   // OK: no leak
    // ...
}

改用 ifstream 文件输入对象可以避免资源泄漏。当函数返回的时候,自动调用析构函数释放内存数据。

注意

所谓的泄漏( leak )通俗的讲,就是出现资源没有及时释放的情况。细分下去,更重要的一类泄漏是:存在无法被释放的资源。比如,你在内存堆中创建了一个对象,然后在程序运行过程中,该对象的地址指针被其他数据覆盖掉了。因为这个对象的地址信息丢失了,你就无法释放这个对象了。所以,对任何中途结束的函数,要么及时释放内存,要么把函数分配的对象返回给上层函数继续使用或者晚些时候释放。

不过,我们也不要求你在程序结束的时候一定要返回某个常驻对象的地址。因为程序结束的时候,系统会自动清理分配给该程序的内存。

然而,最简单、最安全的方法还是应该利用抽象机制隐式地释放资源。

注意

通过强化“生命周期安全规范”,可以避免资源泄漏。再结合 RAII,就不需要垃圾回收机制。再结合“类型和边界检测规范”,我们就可以通过工具实现完整的类型和资源安全。

强化

  • 检查指针的使用。区分有主和无主指针。如以上例子用标准库资源输入流对象替换文件句柄。或者可能的话,用 GSL 中的 owner 所有权指针标记资源所有权。
  • 检查原始的 newdelete 操作。考虑使用定义了释放规则的接口封装:

      shared_ptr<int> p(new int[10][](int* p) {delete []p;})
    
  • 检查返回值是原始指针(raw pointer)的资源分配函数。比如: fopen, malloc, strdup.