C++ 中不同于Java之类的语言,编写者需要对资源进行手动管理。

Use object to manage resources

和C语言一样,C++中也需要对手动申请的内存进行释放。

void bar() {
    int *p = new int;
    //...
    delete p;
}

如果总是依赖于手动调用 delete 释放资源是行不通的。手动管理资源总会出现差错,比如后面修改代码时加上了一句 return ,那么申请的内存将得不到释放。另外,在一下非常复杂的系统中,可能出现资源被多个模块公有,如果简单释放,可能导致其他部分崩溃。

现在需要的是一种自动进行内存管理的机制:把资源放到对象内,依靠C++提供的“析构函数自动调用机制”确保资源的释放。这种机制有两个关键的想法:

  • 获取资源后立刻放进管理对象内
  • 管理对象运用析构函数确保资源被正确释放

实际上“以对象管理资源”的观念通常被称为“资源获取时机便是初始化时机(Resource Acquisition Is Initialization; RAII)。C++中提供了基础的 RAII 类,分别是:

  • std::shared_ptr 及 std::weak_ptr
  • std::unique_ptr

我们改写一下例子:

void bar() {
    std::shared_ptr<int> wrapper(new int);
    //...
}

当然资源不仅仅是内存,也可以是文件描述符、互斥锁等。C++ 的 RAII 类中允许我们定义自己的删除函数,所以可以直接使用之管理其他非内存资源。

Think carefully about copying behavior in resource-managing classes

资源因为其特殊性,所以不能简单拷贝。通常用于处理拷贝的方式有以下两种:

  • 禁止复制
  • 对底层资源进行“引用计数”

unique_ptr 要求对象同一时刻只能拥有一个 owner,而 shared_ptr 则使用引用计数实现;对 unique_ptr 只能进行所有权转移,shared_ptr 则要避免循环计数。

Use the same form in corresponding uses of new and delete

如果你在 new 表达式中使用了[],那么也应该在 delete 中使用[]。当然 C++ 中提供了 vectorstring 等 templates,可以将对数组的要求降为 0。如果你非要使用原生数组,也可以使用 unique_ptr 管理,只需要在类型模板参数后添加[]:

unique_ptr<int[]> arrays(new int[10]);

然而 shared_ptr 并不支持,如果你非要使用,那么请自己定义 delete

store newed objects in smart pointers in standalone statements

C++ 在实现上有很大的弹性,所以编译器可能会对指令进行重排,所以凡是写标准未定义执行顺序的代码,都可能出现问题。例如 C++ 没有规定参数求值,那么求值结果跟顺序有关时,就会出现非预期行为。

如果其中涉及到资源管理,那么可能造成资源泄露,所以 C++ 提供了单独的环境将对象置于资源管理对象中:

int foo() {
    //...
    throw ...;
    //...
}

void bar(std::shared_ptr<int> ptr, int);

bar(std::shared_ptr<int>(new int), foo());  // dangerous

// 上面的代码求值顺序未定义,通过编译器重排后可能导致内存泄露

bar(std::make_shared<int>(), foo());

单独的环境中确保资源能够正确放入资源管理对象,从而保证了不会发生资源泄露。