天天看点

条款13(一):以对象管理资源条款13:以对象管理资源

条款13:以对象管理资源

Use objects to manage resources.

本章分为两个部分。

资源

所谓资源(resources),指的就是:

  • 如果使用了它,将来一定要还给系统。如果不还给系统,就会发送糟糕的事情。

C++程序内,最常用的资源就是动态分配内存(如果分配的内存不归还给系统,就会导致内存泄漏),但是,内存只是需要进行管理的众多资源之一。其他比较常见的资源还有:

  • 文件描述器(file descriptors)
  • 互斥锁(mutex locks)
  • 图形界面中的字型和笔刷
  • 数据库连接
  • 网络sockets

举个例子:

假设我们使用一个用来模拟投资行为的程序库(例如模拟股票、债券等等),其中各式各样的投资类型则继承自一个root class Investment:

class Investment { ... }    //“投资类型”继承体系中的 root class
           

对于这个程序,程序库通过一个工厂函数(factory function),条款7供应特定的Investment对象:

Factory函数会“返回一个base class指针, 指向新生成的derived class对象”。
Investment* createInvestment();   //返回指针,指向Investment继承体系内的动态分配对象。
                                  //调用者有责任删除它。这里为了简化,刻意不写参数。
           

就像上面所说,createInvestment的调用端在使用了函数返回的对象后,有责任将其删除。定义一个实现这个责任的函数f:

void f()
{
    Investment* createInvestment();    //调用factory函数
    ...
    delete pInv;    //释放pInv所指对象
}
           

上面的代码中,虽然开起来是可行的,但是存在着诸多情况下,f无法删除它从createInvestment的投资对象:

  • 原因之一可能是在“ … ”内的代码可能存在过早的return语句。

如果这样的return执行起来,控制流就绝不会触及delete语句。

  • 另一种的原因则是“ … ”内的语句抛出异常。

此时控制流也不会触及delete语句。

  • 总之,无论delete语句因为什么原因被忽略了,所泄漏的不仅是内含投资对象的内存,还有投资对象所保存的所有资源。

因此,为了确保createInvestment返回的资源总是被释放,需要将资源放入到对象内。当控制流离开f,该对象的析构函数会自动释放这些资源。实际上,换句话说:

  • 将资源放到对象内,便可以依赖C++的“析构函数自动调用机制”确保资源被顺利释放。

auto_ptr

许多资源被动态分配在heap中而后用于单一区块或函数内。对于这些资源:

  • 它们应该在控制流离开这个区块或函数时完成释放。

对于这一点,标准程序库提供了auto_ptr来实现这个目标。

  • auto_ptr是一个“类指针(pointer-like)对象”,也就是所谓的“智能指针”,其析构函数自动对其所指对象调用delete。

auto_ptr的使用:

void f()
{
    std::auto_ptr<Investment> pInv(createInvestment());   //调用factory函数,使用pInv
    ...                                                   //经由auto_ptr的析构函数会自动删除pInv
}
           

RAII

由上面的例子,关于“以对象管理对象”的:

1,获得资源后立即放入到管理对象(managing object)之中。在上面的代码中,createInvestment返回的资源被当做它的管理者auto_ptr的初值。实际上,“以对象管理资源”通常被称为:

  • 资源获得时机,便是初始化时机。(Resource Acquisition Is Initialization, RAII)

因为我们几乎总是在获得一笔资源后于同一语句内,用它初始化某个管理对象。另外,有的时候获得的资源被用来赋值(而非初始化)某个管理对象。不管是初始化还是赋值,每一笔资源都是在获得的同时立刻被放进管理对象之中。

2,管理对象(managing object)运用析构函数确保资源被释放。不管控制流是如何离开区块或函数,一旦对象被销毁了(例如当对象离开作用域),其析构函数就自动会被调用,于是资源就被释放了。如果资源释放的动作可能会抛出异常,可以参考条款8中的介绍。

由于auto_ptr被销毁时会自动删除它所指的东西,因此一定要注意,不要让多个auto_ptr同时指向同一个对象。如果对象被删除了一次一上,就会出现“未定义行为”的问题。

针对这个问题,auto_ptr有这样的一个特性来预防这一问题:

  • 若是想要通过copy构造函数或者copy assignment操作符去复制它们,它们就会变成null,而复制所得的指针将会获得资源的唯一拥有权。
std::auto_ptr<Investment> pInv1(createInvestment());    //pInv1指向createInvestment返回物

std::auto_ptr<Investment> pInv2(pInv1);     //(copy构造)现在pInv2指向这个对象,pInv1被设为null

pInv1 = pInv2;     //(copy assignment)现在有变成了pInv1指向这个对象,pInv2被设为null
           

对于上面这种奇怪的复制行为,其底层的逻辑为:

  • 受auto_ptr管理的资源必须绝对没有一个以上的auto_ptr同时指向它。

因为这一特性,auto_ptr并不是管理动态分配资源的最好的选择。例如,STL容器就要求其元素要有“正常的”复制行为,因此这些容器就无法使用auto_ptr。