Effective C++ 学习笔记 第三章:资源管理

第一章见 Effective C++ 学习笔记 第一章:让自己习惯 C++
第二章见 Effective C++ 学习笔记 第二章:构造、析构、赋值运算

计算机资源,除了我们熟悉的内存,还包括其他需要在使用时占用,在使用后归还给系统的东西,还包括如文件描述符、互斥锁、图形控件、数据库连接、网络端口等。

条款 13: 以对象管理资源

Use objects to manage resources.

话题 1:不要让调用者回收资源

不要把 delete 的工作单独留给调用者。有些时候是担心调用者忘记 delete,但更隐晦的情况可能是调用程序写了 delete,但因为一些原因,不会执行到 delete,比如之前的一些异常,或者隐藏很深的 return 语句。程序在维护过程中,逐渐可能会暴露这种问题。
我们应该将资源管理的工作放到对象内,让对象的析构函数来完成资源回收的任务。

话题 2:智能指针

可以将资源赋给智能指针来引用,智能指针可以自动在退出作用域时,回收它指向的资源。
比如 auto_ptr:

class Inv();   // 省略定义
Inv* createInv();  // 工厂函数,返回 Inv 的对象

void f() {
    std::auto_ptr<Inv> pInv(createInv());
    // 退出 f() 作用域时,Inv 中的资源会被自动回收
}

两个关键工作:

  • 获得资源后立即将其放入对象内管理。比如上边代码放到了 auto_ptr pInv 中。 这叫 RAII,即资源取得时便是初始化时(Resource Acquisition Is Initialization)。
  • 管理对象运用析构函数确保资源被释放。比如上边代码中,auto_ptr 的析构函数会释放资源。因为我们要求析构函数中不得抛出异常,所以一旦管理对象要被析构了,它所管理的资源也一定会被释放。

别让多个 auto_ptr 指针指向同一个对象,如果其中一个 auto_ptr 已经析构了,那其他 auto_ptr 将指向已经被释放的资源位置!auto_ptr 有个特殊的性质,它的 copy 构造函数和copy 运算符重载,是会控制唯一性,它们会让 copy 的指针为 null:

std::auto_ptr<Inv> pInv1 (createInv());   // pInv1 指向 Inv 资源
std::auto_ptr<Inv> pInv2 (pInv1);         // 现在 pInv2 指向 Inv 资源,pInv1 为 null
pInv1 = pInv2;                            // 现在 pInv1 指向 Inv 资源,pInv2 为 null

对这个问题的替代方案是 shared_ptr,它是引用计数型智能指针。类似于互斥锁和共享锁的概念,当多个 shared_ptr 指向同一个资源时,它可计数,当计数不为 0 时,析构个别 shared_ptr 不会释放资源,只有当计数为 0 时,才会析构这些 shared_ptr 指向的资源。
但 shared_ptr 不能解决环状引用,也就是两个指针互相指向对方,所以它只是类似于资源回收。
注意,shared_ptr 是 tr1 里边提供的,它的名字空间是 std::tr1::shared_ptr。

auto_ptr 和 shared_ptr 只是 RAII 的一个典型应用,我们自己写的结构中也可以应用 RAII。

话题 3:智能指针不能用来指向数组

auto_ptr 和 shared_ptr 在释放资源时都是用 delete,而不是 delete[],所以它们不应该被用来指向数组结构,而且,编译器也无法检查这种错误,一旦指向了数组结构,那最后在析构时,就无法完全释放资源。
作者的理由是,C++ 中总有办法取代数组,比如 string 和 vector。不过,Boost 库中的 boost::scopted_array 和 boost::shared_array 可以实现指向数组的操作。

其实,createInv() 这个接口本身设计的有问题,它不应该把一个资源直接扔出来。

原书建议

  • 为了防止资源泄漏,请使用 RAII 对象,它们在构造函数中获取资源并在析构函数中释放资源。
  • 两个常被使用的 RAII 对象是 tr1::shared_ptr 和 auto_ptr,前者通常更常用,区别在于 copy 时是否允许多个指针指向同一个资源对象。

条款 14 :在资源管理类中小心 copying 行为

Think carefully about copying behavior in resource-managing classes.

  1. 禁止资源管理类对象的复制,这种操作会导致意外情况。使用 private Uncopyable 方式来处理,见 条款6
  2. 或者,使用引用计数,这和 tr1::shared_ptr 是类似的境况。shared_ptr 支持定义删除器(deleter),默认情况下,auto_ptr 和 shared_ptr 都是通过 delete 来释放资源,但有些时候,我们希望通过其他方式释放资源,比如 unlock(mutex)。
  3. 复制资源管理类时,进行深度拷贝。也就是并不是复制类对象本身,而是复制类对象管理的所有资源,都形成副本。比如 string 的复制,虽然 string 的内容是指向 heap 上字符内容的指针,但复制 string 对象时, heap 上的字符内容都会产生副本。
  4. 控制任何时刻,只允许最多 1 个资源管理类对象来管理资源,就像 auto_ptr 那样子。通过重载两个 copy 函数来实现。

原书建议

  • 复制 RAII 对象必须一并复制它所管理的资源,所以资源的 copying 行为决定 RAII 对象的 copying 行为。
  • 普遍而常见的 RAII classes copying 行为是:禁止 copying、引用计数法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章